use super::{backend_override_label, ResolvedScanConfig};
use crate::stable_hash::StableHasher;
fn format_optional_path<P: AsRef<std::path::Path>>(path: Option<P>, unset_label: &str) -> String {
match path {
Some(path) => path.as_ref().display().to_string(),
None => unset_label.to_string(),
}
}
fn push_limit(
out: &mut String,
name: &str,
available: bool,
feature: &str,
value: impl std::fmt::Display,
) {
if available {
out.push_str(&format!("{name} = {value}\n"));
} else {
out.push_str(&format!(
"{name} = unavailable (requires the `{feature}` feature in this keyhog build)\n"
));
}
}
pub(crate) fn render_effective_config(resolved: &ResolvedScanConfig) -> String {
let s = &resolved.scanner;
let mut out = String::new();
out.push_str("[effective-config]\n");
out.push_str(&format!(
"backend = {}\n",
backend_override_label(resolved.backend_override)
));
out.push_str(&format!("batch_pipeline = {}\n", resolved.batch_pipeline));
out.push_str(&format!(
"threads = {}\n",
resolved
.threads
.map_or_else(|| "auto".to_string(), |n| n.to_string())
));
out.push_str(&format!(
"reader_threads = {}\n",
resolved
.reader_threads
.map_or_else(|| "auto".to_string(), |n| n.to_string())
));
out.push_str(&format!("fused_batch = {}\n", resolved.fused_batch));
out.push_str(&format!(
"fused_depth = {}\n",
resolved
.fused_depth
.map_or_else(|| "auto".to_string(), |n| n.to_string())
));
out.push_str(&format!("gpu = {}\n", resolved.gpu_runtime_policy));
out.push_str(&format!("autoroute_gpu = {}\n", resolved.autoroute_gpu));
out.push_str(&format!(
"autoroute_calibration = {}\n",
resolved.autoroute_calibration
));
out.push_str(&format!("profile = {}\n", s.profile));
out.push_str(&format!("perf_trace = {}\n", s.perf_trace));
out.push_str(&format!("verify = {}\n", resolved.report.verify));
out.push_str(&format!("format = {}\n", resolved.report.format));
out.push_str(&format!(
"severity = {}\n",
resolved
.report
.severity
.as_ref()
.map_or_else(|| "all".to_string(), ToString::to_string)
));
out.push_str(&format!("dedup = {}\n", resolved.report.dedup));
out.push_str(&format!(
"show_secrets = {}\n",
resolved.report.show_secrets
));
out.push_str(&format!(
"hide_client_safe = {}\n",
resolved.report.hide_client_safe
));
out.push_str(&format!(
"suppress_test_fixtures = {}\n",
!resolved.report.no_suppress_test_fixtures
));
out.push_str(&format!("lockdown = {}\n", resolved.report.lockdown));
out.push_str(&format!(
"verify_timeout_secs = {}\n",
resolved.verify.timeout_secs
));
out.push_str(&format!(
"verify_concurrency = {}\n",
resolved.verify.max_concurrent_per_service
));
out.push_str(&format!("verify_rate_rps = {}\n", resolved.verify.rate));
let proxy_policy = match resolved.verify.proxy.as_deref() {
None => "unset",
Some(value) if value.eq_ignore_ascii_case("off") => "off",
Some(_) => "configured",
};
out.push_str(&format!("http_proxy = {proxy_policy}\n"));
out.push_str(&format!(
"insecure_tls = {}\n",
resolved.verify.insecure_tls
));
out.push_str(&format!(
"allow_script_verify = {}\n",
resolved.verify.allow_script_verify
));
out.push_str(&format!("verify_oob = {}\n", resolved.verify.oob.enabled));
out.push_str(&format!(
"verify_oob_timeout_secs = {}\n",
resolved.verify.oob.timeout_secs
));
out.push_str(&format!("min_confidence = {}\n", resolved.min_confidence));
out.push_str(&format!("ml_enabled = {}\n", resolved.ml_enabled));
out.push_str(&format!(
"ml_weight = {}\n",
s.ml_weight_override.map_or_else(
|| "detector-policy".to_string(),
|weight| weight.to_string()
)
));
out.push_str(&format!("entropy_enabled = {}\n", s.entropy_enabled));
out.push_str(&format!(
"entropy_ml_authoritative = {}\n",
s.entropy_ml_authoritative
));
out.push_str(&format!(
"generic_keyword_low_entropy = {}\n",
s.generic_keyword_low_entropy
));
out.push_str(&format!("entropy_threshold = {}\n", s.entropy_threshold));
out.push_str(&format!("min_secret_len = {}\n", s.min_secret_len));
out.push_str(&format!(
"entropy_bpe_max_bytes_per_token = {}\n",
s.entropy_bpe_max_bytes_per_token
));
out.push_str(&format!(
"entropy_bpe_policy = {}\n",
if s.entropy_bpe_max_bytes_per_token_override.is_some() {
"scan-override"
} else {
"scan-fallback"
}
));
out.push_str(&format!(
"entropy_in_source_files = {}\n",
s.entropy_in_source_files
));
out.push_str(&format!("max_decode_depth = {}\n", s.max_decode_depth));
out.push_str(&format!("max_decode_bytes = {}\n", s.max_decode_bytes));
out.push_str(&format!("validate_decode = {}\n", s.validate_decode));
out.push_str(&format!(
"per_chunk_timeout_ms = {}\n",
s.per_chunk_timeout_ms
.map_or_else(|| "off".to_string(), |ms| ms.to_string())
));
out.push_str(&format!(
"regex_dfa_limit = {}\n",
resolved.regex_dfa_limit.map_or_else(
|| format!("{} (default)", keyhog_scanner::regex_dfa_limit_default()),
|bytes| bytes.to_string()
)
));
out.push_str(&format!(
"gpu_batch_input_limit = {}\n",
resolved.gpu_batch_input_limit.map_or_else(
|| "VRAM-adaptive (default)".to_string(),
|bytes| bytes.to_string()
)
));
out.push_str(&format!(
"max_file_size = {}\n",
resolved.max_file_size.map_or_else(
|| format!("{} (default)", keyhog_core::DEFAULT_MAX_FILE_SIZE_BYTES),
|bytes| bytes.to_string()
)
));
#[cfg(feature = "git")]
out.push_str(&format!("max_commits = {}\n", resolved.max_commits));
out.push_str(&format!(
"no_default_excludes = {}\n",
resolved.no_default_excludes
));
out.push_str(&format!(
"exclude_paths = {}\n",
resolved.exclude_paths.len()
));
out.push_str(&format!("incremental = {}\n", resolved.incremental));
let incremental_cache = format_optional_path(
resolved.incremental_cache_path.as_ref(),
"<platform default>",
);
out.push_str(&format!("incremental_cache = {incremental_cache}\n"));
let limits = resolved.source_limits;
push_limit(&mut out, "limit_stdin_bytes", true, "", limits.stdin_bytes);
push_limit(
&mut out,
"limit_web_response_bytes",
cfg!(feature = "web"),
"web",
limits.web_response_bytes,
);
push_limit(
&mut out,
"limit_s3_object_bytes",
cfg!(feature = "s3"),
"s3",
limits.s3_object_bytes,
);
push_limit(
&mut out,
"limit_gcs_object_bytes",
cfg!(feature = "gcs"),
"gcs",
limits.gcs_object_bytes,
);
push_limit(
&mut out,
"limit_azure_blob_bytes",
cfg!(feature = "azure"),
"azure",
limits.azure_blob_bytes,
);
push_limit(
&mut out,
"limit_cloud_max_objects",
cfg!(any(feature = "s3", feature = "gcs", feature = "azure")),
"s3/gcs/azure",
limits.cloud_max_objects,
);
push_limit(
&mut out,
"limit_docker_tar_entry_bytes",
cfg!(feature = "docker"),
"docker",
limits.docker_tar_entry_bytes,
);
push_limit(
&mut out,
"limit_docker_image_config_bytes",
cfg!(feature = "docker"),
"docker",
limits.docker_image_config_bytes,
);
push_limit(
&mut out,
"limit_docker_tar_total_bytes",
cfg!(feature = "docker"),
"docker",
limits.docker_tar_total_bytes,
);
push_limit(
&mut out,
"limit_git_line_bytes",
cfg!(feature = "git"),
"git",
limits.git_line_bytes,
);
push_limit(
&mut out,
"limit_git_total_bytes",
cfg!(feature = "git"),
"git",
limits.git_total_bytes,
);
push_limit(
&mut out,
"limit_git_blob_bytes",
cfg!(feature = "git"),
"git",
limits.git_blob_bytes,
);
push_limit(
&mut out,
"limit_git_chunks",
cfg!(feature = "git"),
"git",
limits.git_chunk_count,
);
push_limit(
&mut out,
"limit_hosted_git_pages",
cfg!(any(
feature = "github",
feature = "gitlab",
feature = "bitbucket"
)),
"github/gitlab/bitbucket",
limits.hosted_git_pages,
);
push_limit(
&mut out,
"limit_binary_read_bytes",
cfg!(feature = "binary"),
"binary",
limits.binary_read_bytes,
);
push_limit(
&mut out,
"limit_binary_decompiled_bytes",
cfg!(feature = "binary"),
"binary",
limits.binary_decompiled_bytes,
);
out.push_str(&format!("scan_comments = {}\n", s.scan_comments));
out.push_str(&format!(
"unicode_normalization = {}\n",
s.unicode_normalization
));
out.push_str(&format!(
"disabled_detectors = {}\n",
resolved.disabled_detectors.len()
));
let cache_dir =
format_optional_path(resolved.hyperscan_cache_dir.as_ref(), "<platform default>");
out.push_str(&format!("hyperscan_cache_dir = {cache_dir}\n"));
let autoroute_cache_path =
format_optional_path(resolved.autoroute_cache_path.as_ref(), "<disabled>");
out.push_str(&format!("autoroute_cache_path = {autoroute_cache_path}\n"));
let matcher_cache_path =
format_optional_path(resolved.matcher_cache_path.as_ref(), "<disabled>");
out.push_str(&format!("matcher_cache_path = {matcher_cache_path}\n"));
let calibration_cache_path =
format_optional_path(resolved.calibration_cache_path.as_ref(), "<disabled>");
out.push_str(&format!(
"calibration_cache_path = {calibration_cache_path}\n"
));
out.push_str(&format!(
"calibration_entries = {}\n",
resolved.calibration_entry_count
));
out.push_str(&format!(
"calibration_digest = {:016x}\n",
resolved.calibration_digest
));
out.push_str(&format!(
"aws_canary_accounts = {}\n",
resolved.aws_canary_accounts.len()
));
let allowlist_file = match resolved.allowlist.file.as_ref() {
Some(path) => path.display().to_string(),
None => "<scan-root>/.keyhogignore".to_string(),
};
out.push_str(&format!("allowlist_file = {allowlist_file}\n"));
out.push_str(&format!(
"allowlist_require_reason = {}\n",
resolved.allowlist.require_reason
));
out.push_str(&format!(
"allowlist_require_approved_by = {}\n",
resolved.allowlist.require_approved_by
));
let max_expires_days = match resolved.allowlist.max_expires_days {
Some(days) => days.to_string(),
None => "off".to_string(),
};
out.push_str(&format!(
"allowlist_max_expires_days = {max_expires_days}\n"
));
let tuning = resolved.scanner_tuning.effective();
out.push_str(&format!("tuning_fallback_hs = {}\n", tuning.fallback_hs));
out.push_str(&format!(
"tuning_hs_prefilter_max_len = {}\n",
tuning.hs_prefilter_max_len
));
out.push_str(&format!(
"tuning_hs_shard_target = {}\n",
tuning.hs_shard_target
));
out.push_str(&format!(
"tuning_fallback_anchor = {}\n",
tuning.fallback_anchor
));
out.push_str(&format!(
"tuning_homoglyph_gate = {}\n",
tuning.homoglyph_gate
));
out.push_str(&format!(
"tuning_homoglyph_ascii_skip = {}\n",
tuning.homoglyph_ascii_skip
));
out.push_str(&format!(
"tuning_fallback_reverse = {}\n",
tuning.fallback_reverse
));
out.push_str(&format!(
"tuning_prefilter_truncate = {}\n",
tuning.prefilter_truncate
));
out.push_str(&format!(
"tuning_fallback_prefix_gate = {}\n",
tuning.fallback_prefix_gate
));
out.push_str(&format!("tuning_decode_focus = {}\n", tuning.decode_focus));
out.push_str(&format!(
"tuning_confirmed_suffix_gate = {}\n",
tuning.confirmed_suffix_gate
));
out.push_str(&format!(
"tuning_confirmed_companion_gate = {}\n",
tuning.confirmed_companion_gate
));
out.push_str(&format!(
"tuning_no_candidate_gate = {}\n",
tuning.no_candidate_gate
));
out.push_str(&format!(
"tuning_fallback_localizer = {}\n",
tuning.fallback_localizer
));
out.push_str(&format!(
"tuning_gpu_recall_floor = {}\n",
tuning.gpu_recall_floor
));
out.push_str(&format!(
"tuning_chunk_lane_threshold = {}\n",
tuning.chunk_lane_threshold
));
out.push_str(&format!("known_prefixes = {}\n", s.known_prefixes.len()));
out.push_str(&format!("secret_keywords = {}\n", s.secret_keywords.len()));
out.push_str(&format!("test_keywords = {}\n", s.test_keywords.len()));
out.push_str(&format!(
"placeholder_keywords = {}\n",
s.placeholder_keywords.len()
));
let mut floors: Vec<(&String, &f64)> = resolved.detector_min_confidence.iter().collect();
floors.sort_by(|a, b| a.0.cmp(b.0));
for (id, floor) in floors {
out.push_str(&format!("detector_min_confidence.{id} = {floor}\n"));
}
out
}
fn autoroute_config_hasher(resolved: &ResolvedScanConfig) -> StableHasher {
let mut h = StableHasher::new("autoroute-config-digest");
let s = &resolved.scanner;
h.field_bool("scanner.profile", s.profile);
h.field_bool("scanner.perf_trace", s.perf_trace);
h.field_f64_bits("scanner.min_confidence", s.min_confidence);
h.field_bool("scanner.ml_enabled", s.ml_enabled);
h.field_bool(
"scanner.ml_weight_override.present",
s.ml_weight_override.is_some(),
);
if let Some(weight) = s.ml_weight_override {
h.field_f64_bits("scanner.ml_weight_override.value", weight);
}
h.field_bool("scanner.entropy_enabled", s.entropy_enabled);
h.field_bool(
"scanner.entropy_ml_authoritative",
s.entropy_ml_authoritative,
);
h.field_bool(
"scanner.generic_keyword_low_entropy",
s.generic_keyword_low_entropy,
);
h.field_f64_bits("scanner.entropy_threshold", s.entropy_threshold);
h.field_usize("scanner.min_secret_len", s.min_secret_len);
h.field_f64_bits(
"scanner.entropy_bpe_max_bytes_per_token",
s.entropy_bpe_max_bytes_per_token,
);
h.field_bool(
"scanner.entropy_bpe_max_bytes_per_token_override.present",
s.entropy_bpe_max_bytes_per_token_override.is_some(),
);
if let Some(bound) = s.entropy_bpe_max_bytes_per_token_override {
h.field_f64_bits(
"scanner.entropy_bpe_max_bytes_per_token_override.value",
bound,
);
}
h.field_bool("scanner.entropy_in_source_files", s.entropy_in_source_files);
h.field_usize("scanner.max_decode_depth", s.max_decode_depth);
h.field_usize("scanner.max_decode_bytes", s.max_decode_bytes);
h.field_bool("scanner.validate_decode", s.validate_decode);
h.field_option_u64("scanner.per_chunk_timeout_ms", s.per_chunk_timeout_ms);
h.field_usize("scanner.max_matches_per_chunk", s.max_matches_per_chunk);
h.field_bool("scanner.scan_comments", s.scan_comments);
h.field_bool("scanner.unicode_normalization", s.unicode_normalization);
h.field_bool("scanner.penalize_test_paths", s.penalize_test_paths);
h.field_usize(
"scanner.multiline.max_join_lines",
s.multiline.max_join_lines,
);
h.field_bool(
"scanner.multiline.python_implicit",
s.multiline.python_implicit,
);
h.field_bool(
"scanner.multiline.backslash_continuation",
s.multiline.backslash_continuation,
);
h.field_bool(
"scanner.multiline.plus_concatenation",
s.multiline.plus_concatenation,
);
h.field_bool(
"scanner.multiline.dot_concatenation",
s.multiline.dot_concatenation,
);
h.field_bool(
"scanner.multiline.template_literals",
s.multiline.template_literals,
);
hash_strings(&mut h, "scanner.known_prefixes", &s.known_prefixes);
hash_strings(&mut h, "scanner.secret_keywords", &s.secret_keywords);
hash_strings(&mut h, "scanner.test_keywords", &s.test_keywords);
hash_strings(
&mut h,
"scanner.placeholder_keywords",
&s.placeholder_keywords,
);
h.field_f64_bits("resolved.min_confidence", resolved.min_confidence);
h.field_bool("resolved.ml_enabled", resolved.ml_enabled);
let mut floors: Vec<_> = resolved.detector_min_confidence.iter().collect();
floors.sort_by(|a, b| a.0.cmp(b.0));
h.field_usize("detector_min_confidence.len", floors.len());
for (id, floor) in floors {
h.field_str("detector_min_confidence.id", id);
h.field_f64_bits("detector_min_confidence.floor", *floor);
}
let mut disabled: Vec<_> = resolved.disabled_detectors.iter().collect();
disabled.sort();
h.field_usize("disabled_detectors.len", disabled.len());
for id in disabled {
h.field_str("disabled_detectors.id", id);
}
h.field_bool("require_lockdown", resolved.require_lockdown);
h.field_str(
"backend_override",
backend_override_label(resolved.backend_override),
);
h.field_bool("batch_pipeline", resolved.batch_pipeline);
h.field_option_usize("threads", resolved.threads);
h.field_option_usize("reader_threads", resolved.reader_threads);
h.field_usize("fused_batch", resolved.fused_batch);
h.field_option_usize("fused_depth", resolved.fused_depth);
h.field_usize("fused_batch_bytes", super::runtime::FUSED_BATCH_BYTES);
h.field_str(
"gpu_runtime_policy",
&resolved.gpu_runtime_policy.to_string(),
);
h.field_option_usize("regex_dfa_limit", resolved.regex_dfa_limit);
h.field_option_usize("gpu_batch_input_limit", resolved.gpu_batch_input_limit);
h.field_option_usize("source_policy.max_file_size", resolved.max_file_size);
#[cfg(feature = "git")]
h.field_usize("source_policy.max_commits", resolved.max_commits);
h.field_bool(
"source_policy.no_default_excludes",
resolved.no_default_excludes,
);
let mut exclude_paths = resolved.exclude_paths.clone();
exclude_paths.sort();
hash_strings(&mut h, "source_policy.exclude_paths", &exclude_paths);
h.field_bool("source_policy.incremental", resolved.incremental);
h.field_option_path(
"source_policy.incremental_cache_path",
resolved.incremental_cache_path.as_deref(),
);
h.field_option_path(
"hyperscan_cache_dir",
resolved.hyperscan_cache_dir.as_deref(),
);
hash_strings(&mut h, "aws_canary_accounts", &resolved.aws_canary_accounts);
hash_scanner_tuning(&mut h, &resolved.scanner_tuning);
h.field_option_path("allowlist.file", resolved.allowlist.file.as_deref());
h.field_bool(
"allowlist.require_reason",
resolved.allowlist.require_reason,
);
h.field_bool(
"allowlist.require_approved_by",
resolved.allowlist.require_approved_by,
);
h.field_option_u64(
"allowlist.max_expires_days",
resolved.allowlist.max_expires_days,
);
hash_source_limits(&mut h, resolved.source_limits);
h
}
pub(crate) fn autoroute_config_digest(resolved: &ResolvedScanConfig) -> u64 {
autoroute_config_hasher(resolved).finish_u64()
}
pub(crate) fn profiling_policy_digest(resolved: &ResolvedScanConfig) -> [u8; 32] {
autoroute_config_hasher(resolved).finish_256()
}
pub(crate) fn profiling_resolved_config_digest(resolved: &ResolvedScanConfig) -> [u8; 32] {
let policy_digest = profiling_policy_digest(resolved);
let mut h = StableHasher::new("profile-resolved-config-digest-v1");
h.field_bytes("performance_policy_digest", &policy_digest);
h.field_bool("autoroute_gpu", resolved.autoroute_gpu);
h.field_bool("autoroute_calibration", resolved.autoroute_calibration);
h.field_option_path(
"autoroute_cache_path",
resolved.autoroute_cache_path.as_deref(),
);
h.field_option_path("matcher_cache_path", resolved.matcher_cache_path.as_deref());
h.field_option_path(
"calibration_cache_path",
resolved.calibration_cache_path.as_deref(),
);
h.field_usize("calibration_entry_count", resolved.calibration_entry_count);
h.field_u64("calibration_digest", resolved.calibration_digest);
let report = &resolved.report;
h.field_str("report.format", &report.format.to_string());
let severity = report.severity.as_ref().map(ToString::to_string);
h.field_option_str("report.severity", severity.as_deref());
h.field_str("report.dedup", &report.dedup.to_string());
h.field_bool("report.verify", report.verify);
h.field_bool("report.lockdown", report.lockdown);
h.field_bool("report.show_secrets", report.show_secrets);
h.field_bool(
"report.no_suppress_test_fixtures",
report.no_suppress_test_fixtures,
);
h.field_bool("report.hide_client_safe", report.hide_client_safe);
let verify = &resolved.verify;
h.field_f64_bits("verify.rate", verify.rate);
h.field_usize(
"verify.max_concurrent_per_service",
verify.max_concurrent_per_service,
);
h.field_u64("verify.timeout_secs", verify.timeout_secs);
h.field_option_str("verify.proxy", verify.proxy.as_deref());
h.field_bool("verify.insecure_tls", verify.insecure_tls);
h.field_bool("verify.allow_script_verify", verify.allow_script_verify);
h.field_bool("verify.oob.enabled", verify.oob.enabled);
#[cfg(feature = "verify")]
h.field_str("verify.oob.server", &verify.oob.server);
h.field_u64("verify.oob.timeout_secs", verify.oob.timeout_secs);
h.finish_256()
}
pub(crate) fn matcher_resolved_config_digest(resolved: &ResolvedScanConfig) -> [u8; 32] {
let mut h = StableHasher::new("matcher-resolved-config-digest-v3");
hash_scanner_tuning(&mut h, &resolved.scanner_tuning);
let mut disabled: Vec<_> = resolved.disabled_detectors.iter().collect();
disabled.sort();
h.field_usize("disabled_detectors.len", disabled.len());
for id in disabled {
h.field_str("disabled_detectors.id", id);
}
let mut floors: Vec<_> = resolved.detector_min_confidence.iter().collect();
floors.sort_by(|a, b| a.0.cmp(b.0));
h.field_usize("detector_min_confidence.len", floors.len());
for (id, floor) in floors {
h.field_str("detector_min_confidence.id", id);
h.field_f64_bits("detector_min_confidence.floor", *floor);
}
h.field_option_usize("regex_dfa_limit", resolved.regex_dfa_limit);
h.finish_256()
}
fn hash_strings(h: &mut StableHasher, field: &str, strings: &[String]) {
h.field_usize(&format!("{field}.len"), strings.len());
for s in strings {
h.field_str(field, s);
}
}
fn hash_scanner_tuning(h: &mut StableHasher, tuning: &keyhog_scanner::ScannerTuningConfig) {
let tuning = tuning.effective();
h.field_bool("scanner_tuning.fallback_hs", tuning.fallback_hs);
h.field_usize(
"scanner_tuning.hs_prefilter_max_len",
tuning.hs_prefilter_max_len,
);
h.field_usize("scanner_tuning.hs_shard_target", tuning.hs_shard_target);
h.field_bool("scanner_tuning.fallback_anchor", tuning.fallback_anchor);
h.field_bool("scanner_tuning.homoglyph_gate", tuning.homoglyph_gate);
h.field_bool(
"scanner_tuning.homoglyph_ascii_skip",
tuning.homoglyph_ascii_skip,
);
h.field_bool("scanner_tuning.fallback_reverse", tuning.fallback_reverse);
h.field_bool(
"scanner_tuning.prefilter_truncate",
tuning.prefilter_truncate,
);
h.field_bool(
"scanner_tuning.fallback_prefix_gate",
tuning.fallback_prefix_gate,
);
h.field_bool("scanner_tuning.decode_focus", tuning.decode_focus);
h.field_bool(
"scanner_tuning.confirmed_suffix_gate",
tuning.confirmed_suffix_gate,
);
h.field_bool(
"scanner_tuning.confirmed_companion_gate",
tuning.confirmed_companion_gate,
);
h.field_bool("scanner_tuning.no_candidate_gate", tuning.no_candidate_gate);
h.field_bool(
"scanner_tuning.fallback_localizer",
tuning.fallback_localizer,
);
h.field_bool("scanner_tuning.gpu_recall_floor", tuning.gpu_recall_floor);
h.field_usize(
"scanner_tuning.chunk_lane_threshold",
tuning.chunk_lane_threshold,
);
}
fn hash_source_limits(h: &mut StableHasher, limits: keyhog_sources::SourceLimits) {
h.field_usize("source_limits.stdin_bytes", limits.stdin_bytes);
h.field_usize(
"source_limits.web_response_bytes",
limits.web_response_bytes,
);
h.field_u64("source_limits.s3_object_bytes", limits.s3_object_bytes);
h.field_u64("source_limits.gcs_object_bytes", limits.gcs_object_bytes);
h.field_u64("source_limits.azure_blob_bytes", limits.azure_blob_bytes);
h.field_usize("source_limits.cloud_max_objects", limits.cloud_max_objects);
h.field_u64(
"source_limits.docker_tar_entry_bytes",
limits.docker_tar_entry_bytes,
);
h.field_u64(
"source_limits.docker_image_config_bytes",
limits.docker_image_config_bytes,
);
h.field_u64(
"source_limits.docker_tar_total_bytes",
limits.docker_tar_total_bytes,
);
h.field_usize("source_limits.git_line_bytes", limits.git_line_bytes);
h.field_usize("source_limits.git_total_bytes", limits.git_total_bytes);
h.field_u64("source_limits.git_blob_bytes", limits.git_blob_bytes);
h.field_usize("source_limits.git_chunk_count", limits.git_chunk_count);
h.field_usize("source_limits.hosted_git_pages", limits.hosted_git_pages);
h.field_usize("source_limits.binary_read_bytes", limits.binary_read_bytes);
h.field_u64(
"source_limits.binary_decompiled_bytes",
limits.binary_decompiled_bytes,
);
}