M0: compilable skeleton — Kigi 0.1.0 fork surgery

Hard fork of xai-org/grok-build (Apache-2.0) re-targeted as Kigi, an
unofficial Kimi Code CLI community build.

Rename & identity
- 72 xai-*/xai-grok-* crates -> kigi-* (explicit: xai-grok-pager-bin ->
  kigi-bin [binary `kigi`], xai-grok-pager -> kigi-tui; rest mechanical);
  ptyctl, ptyctl-cli, third_party/ unchanged; proto package
  xai.grok.tools.v1 -> kigi.tools.v1
- Config home ~/.kigi (KIGI_SHARE_DIR override), env prefix GROK_* ->
  KIGI_*, `kigi --version` carries the unofficial-community-build notice
- clap identity, help text, startup banner, prompt templates rebranded
  (templates re-encrypted)

Deletions (PRD removal list #5/#6/#7/#9/#10)
- voice input (xai-grok-voice) and all TUI wiring
- telemetry: Mixpanel client, external OTel stream, Sentry, OTLP layers,
  trace/GCS/S3 upload queues (kigi-file-utils halved), workspace upload
  module & dc_log, heap-profile uploader, auth-diagnostics uploader,
  session-analytics halves of feedback; local zero-egress observability
  preserved in new kigi-log crate (unified log, --debug firehose,
  subsystem file logs, opt-in instrumentation)
- announcements (crate, remote-settings fields, TUI surfaces)
- plugin marketplace (crate, sources/browse/CTA/extensions-modal tab);
  direct plugin install/uninstall/update via kigi-agent git_install kept
- relay/gateway/assets endpoints and features (agent relay, headless
  relay transport, gateway bridge, LeaderEnvUrls); leader IPC socket now
  ~/.kigi/leader.sock + KIGI_LEADER_SOCKET, no ws-url derivation
- functional types rehomed instead of deleted: PermissionMode ->
  kigi-config-types, McpInitStrategy -> kigi-mcp, PrCreationSource ->
  session signals, TerminalDiagnostics -> kigi-pager-render, agent_id ->
  shell util

Endpoints
- kigi-env rewritten: single production KigiEndpoints {coding_api_base_url
  https://api.kimi.com/coding/v1 (KIGI_CODE_BASE_URL), oauth_host
  https://auth.kimi.com (KIGI_OAUTH_HOST), update_base_url (GitHub
  Releases API), upgrade_page_url}; GrokBuildEnvironment enum deleted

Toolchain & workspace hygiene
- Rust 1.97.0 pinned; edition 2024; full cargo update; git2 hoisted to
  workspace at 0.21 (Option->Result API migration), quick-xml 0.41
- Root Cargo.toml hand-maintained (PRD §8.1): version 0.1.0 inherited by
  all members, members sorted, unused deps pruned
- cargo-deny advisories gate (deny.toml with documented transitive
  exceptions); CI workflow (check/clippy/fmt/deny/test, macOS+Linux)
- cross-crate test seams re-gated behind `test-support` cargo feature;
  insta snapshot baselines renamed to the kigi_tui prefix
- clippy --workspace --all-targets: zero warnings; fmt clean

Fixes surfaced by the port
- updater probe/installer divergence (bin/kigi vs bin/grok symlink set)
- idle model-metadata refresh dead under KIGI_CODE_BASE_URL override
  (new is_effective_coding_endpoint_url, loopback+override aware)
- macOS symlinked-TMPDIR fixture canonicalization (foreign_sessions,
  fast-worktree); RSS measurement tests serialized via serial_test

Docs & legal (Apache §4)
- NOTICE added (upstream attribution + change statement); THIRD-PARTY
  notices sustained; kigi-tools ported-code notices extended; README,
  CONTRIBUTING, SECURITY, AGENTS.md rewritten

Out of scope for M0 (tracked): Kimi auth/inference (M1), search/fetch,
command parity, config import (M2), Computer Hub excision & final
brand-token sweep (M2), distribution & self-update rewrite (M3).
This commit is contained in:
2026-07-17 05:31:01 -04:00
commit d6c20fc13f
2612 changed files with 1353757 additions and 0 deletions
+706
View File
@@ -0,0 +1,706 @@
//! TOML loading, layered merging, and `$VAR` expansion.
//!
//! The merged result is the **default** config; requirements layers
//! sit on top via [`crate::validation`].
use std::path::Path;
use crate::paths::{system_config_dir, user_kigi_home};
use crate::validation::{load_requirements, load_system_requirements};
use crate::version_overrides::{self, apply_version_overrides};
/// Load and parse a TOML file, expanding `$VAR` references. Empty table if absent.
pub fn load_toml_file(path: &Path) -> std::io::Result<toml::Value> {
match std::fs::read_to_string(path) {
Ok(s) => match toml::from_str::<toml::Value>(&s) {
Ok(mut v) => {
expand_env_vars_in_toml(&mut v);
Ok(v)
}
Err(e) => {
// Built from the span, never from Display — Display echoes the
// offending source line, which may carry a secret. Safe to log and
// to return to a client.
let detail = toml_error_detail(&s, &e);
tracing::error!(file = %path.display(), "config toml has syntax errors: {detail}");
Err(std::io::Error::other(detail))
}
},
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
Ok(toml::Value::Table(toml::map::Map::new()))
}
Err(e) => {
tracing::error!(file = %path.display(), "config file unreadable: {e}");
Err(e)
}
}
}
/// A snippet-free description of a TOML parse error: `"TOML parse error at line
/// L, column C: <what>"` (or just the message when there's no span). Never
/// includes the offending source line — `Display` echoes it and it may carry a
/// secret — so this is safe to log or surface to a client. Shared with the trace
/// `config_files` artifact so the redaction rule lives in one place.
pub fn toml_error_detail(src: &str, e: &toml::de::Error) -> String {
match e.span() {
Some(span) => {
let (line, col) = line_col(src, span.start);
format!(
"TOML parse error at line {line}, column {col}: {}",
e.message()
)
}
None => e.message().to_owned(),
}
}
/// 1-based (line, column) of a byte offset within `src`.
fn line_col(src: &str, byte: usize) -> (usize, usize) {
let mut line = 1;
let mut col = 1;
for (i, ch) in src.char_indices() {
if i >= byte {
break;
}
if ch == '\n' {
line += 1;
col = 1;
} else {
col += 1;
}
}
(line, col)
}
/// [`load_toml_file`] plus that layer's `[[version_overrides]]`. Use for
/// grok config files; use [`load_toml_file`] directly for unrelated TOML.
pub fn load_config_file(path: &Path) -> std::io::Result<toml::Value> {
let mut v = load_toml_file(path)?;
apply_version_overrides_with_registered(&mut v)?;
Ok(v)
}
pub fn load_from_disk() -> std::io::Result<toml::Value> {
load_user_config_layer(user_kigi_home().as_deref(), "config.toml")
}
/// Managed config filename, shared by the loaders in this module.
pub const MANAGED_CONFIG_FILENAME: &str = "managed_config.toml";
pub fn load_managed_config() -> std::io::Result<toml::Value> {
load_user_config_layer(user_kigi_home().as_deref(), MANAGED_CONFIG_FILENAME)
}
/// Load a user-tier config layer from `<home>/<filename>`. With no resolvable
/// user home, returns an empty table rather than reading a cwd-relative
/// `.kigi/<filename>` (the cwd-fallback would silently promote an untrusted
/// project `.kigi` to the user tier).
fn load_user_config_layer(home: Option<&Path>, filename: &str) -> std::io::Result<toml::Value> {
match home {
Some(g) => load_config_file(&g.join(filename)),
None => Ok(toml::Value::Table(toml::map::Map::new())),
}
}
pub fn load_system_managed_config() -> std::io::Result<toml::Value> {
let mut v = match system_config_dir() {
Some(dir) => load_toml_file(&dir.join(MANAGED_CONFIG_FILENAME))?,
None => toml::Value::Table(toml::map::Map::new()),
};
apply_version_overrides_with_registered(&mut v)?;
Ok(v)
}
/// One managed-config layer: the parsed TOML and the file it came from.
#[derive(Debug, Clone)]
pub struct ManagedConfigLayer {
pub value: toml::Value,
pub path: std::path::PathBuf,
/// `true` for the root-owned system layer (`/etc/kigi`), derived from the
/// load directory.
pub is_system: bool,
}
/// All `managed_config.toml` layers in apply order (system first, user last).
/// Absent layers are skipped; unparsable layers are skipped with a warning.
/// One bad layer never drops the others.
pub fn managed_config_layers() -> Vec<ManagedConfigLayer> {
managed_config_layers_at(system_config_dir().as_deref(), user_kigi_home().as_deref())
}
/// [`managed_config_layers`] with explicit directories.
pub fn managed_config_layers_at(
system_dir: Option<&Path>,
user_home: Option<&Path>,
) -> Vec<ManagedConfigLayer> {
let mut layers = Vec::new();
for (dir, is_system) in [(system_dir, true), (user_home, false)] {
let Some(path) = dir.map(|d| d.join(MANAGED_CONFIG_FILENAME)) else {
continue;
};
if !path.is_file() {
continue;
}
match load_config_file(&path) {
Ok(value) => layers.push(ManagedConfigLayer {
value,
path,
is_system,
}),
Err(e) => {
tracing::warn!(path = %path.display(), error = %e, "skipping managed_config.toml layer that failed to load or parse")
}
}
}
layers
}
/// Layers lowest→highest priority. `[[campaigns]]` taken off each layer at load.
#[derive(Clone)]
pub struct ConfigLayers {
pub system_managed: toml::Value,
pub managed: toml::Value,
pub user: toml::Value,
pub user_requirements: Option<toml::Value>,
pub system_requirements: Option<toml::Value>,
/// macOS MDM requirements; highest requirements tier when present.
pub mdm_requirements: Option<toml::Value>,
pub campaigns: crate::campaigns::CampaignOverrides,
}
impl Default for ConfigLayers {
fn default() -> Self {
Self {
system_managed: toml::Value::Table(Default::default()),
managed: toml::Value::Table(Default::default()),
user: toml::Value::Table(Default::default()),
user_requirements: None,
system_requirements: None,
mdm_requirements: None,
campaigns: crate::campaigns::CampaignOverrides::default(),
}
}
}
impl ConfigLayers {
pub fn load() -> std::io::Result<Self> {
use crate::campaigns::{CampaignOverrides, take_campaign_entries};
let mut system_managed = load_system_managed_config()?;
let system_managed_campaigns = take_campaign_entries(&mut system_managed, "system_managed");
let mut managed = load_managed_config()?;
let managed_campaigns = take_campaign_entries(&mut managed, "managed");
let mut user = load_from_disk()?;
let user_campaigns = take_campaign_entries(&mut user, "user");
let mut user_requirements = load_requirements();
let mut system_requirements = load_system_requirements();
let mut mdm_requirements = crate::validation::mdm_requirements_value();
// Highest-authority requirements tier first: `merge_campaign_entries` is
// first-id-wins, so a duplicate campaign id must resolve mdm > system >
// user — matching the layer precedence in `effective_config_base` (where
// mdm is merged last/highest).
let mut requirements_campaigns = Vec::new();
if let Some(ref mut req) = mdm_requirements {
requirements_campaigns.extend(take_campaign_entries(req, "requirements"));
}
if let Some(ref mut req) = system_requirements {
requirements_campaigns.extend(take_campaign_entries(req, "requirements"));
}
if let Some(ref mut req) = user_requirements {
requirements_campaigns.extend(take_campaign_entries(req, "requirements"));
}
Ok(Self {
system_managed,
managed,
user,
user_requirements,
system_requirements,
mdm_requirements,
campaigns: CampaignOverrides {
requirements: requirements_campaigns,
user: user_campaigns,
managed: managed_campaigns,
system_managed: system_managed_campaigns,
},
})
}
/// Layer merge only (no campaign overlay).
pub fn effective_config_base(&self) -> toml::Value {
let mut merged = self.system_managed.clone();
deep_merge_toml(&mut merged, &self.managed);
deep_merge_toml(&mut merged, &self.user);
if let Some(req) = &self.user_requirements {
deep_merge_toml(&mut merged, req);
}
if let Some(sys_req) = &self.system_requirements {
deep_merge_toml(&mut merged, sys_req);
}
if let Some(mdm_req) = &self.mdm_requirements {
deep_merge_toml(&mut merged, mdm_req);
}
merged
}
/// Campaign source slices in priority order (first id wins):
/// requirements > remote > user > managed > system_managed. Single source of
/// truth for the precedence; both this crate and the shell resolver consume it.
pub fn campaign_source_slices<'a>(
&'a self,
remote_campaigns: &'a [crate::campaigns::CampaignEntry],
) -> [&'a [crate::campaigns::CampaignEntry]; 5] {
[
&self.campaigns.requirements,
remote_campaigns,
&self.campaigns.user,
&self.campaigns.managed,
&self.campaigns.system_managed,
]
}
/// Active campaigns against `base`: kill switch → priority merge (first-id-wins)
/// → drop dismissed. The single place disk campaign resolution lives; the shell
/// wraps this with the `KIGI_CAMPAIGNS_OVERRIDE` env layer.
pub fn resolve_campaigns(
&self,
base: &toml::Value,
remote_campaigns: &[crate::campaigns::CampaignEntry],
dismissed_ids: &std::collections::HashSet<String>,
) -> Vec<crate::campaigns::CampaignEntry> {
if campaigns_application_disabled(base) {
return Vec::new();
}
let merged = crate::campaigns::merge_campaign_entries(
&self.campaign_source_slices(remote_campaigns),
);
crate::campaigns::filter_active_campaigns(merged, dismissed_ids)
}
/// Re-merge the requirements layers so an admin's `requirements.toml` always
/// wins over a campaign overlay, regardless of the campaign's source layer.
/// Campaigns are full-power (any field), so this is the structural guarantee
/// that a lower-trust layer's campaign can't override an admin-set field.
fn reapply_requirements(&self, merged: &mut toml::Value) {
for req in [
&self.user_requirements,
&self.system_requirements,
&self.mdm_requirements,
]
.into_iter()
.flatten()
{
deep_merge_toml(merged, req);
}
}
/// Apply active campaign patches onto `merged`, then restore requirements
/// precedence. The single overlay step shared by this crate and the shell.
pub fn apply_campaign_overrides(
&self,
merged: &mut toml::Value,
active: &[crate::campaigns::CampaignEntry],
) {
crate::campaigns::apply_active_campaign_patches(merged, active);
self.reapply_requirements(merged);
}
/// Layer merge + disk/remote campaign overlay, honoring the kill switch. The
/// shell's `load_effective_config` is the remote/override-aware path; this is
/// used by `effective_config_disk_only` and tests.
pub fn effective_config_with_campaigns(
&self,
remote_campaigns: &[crate::campaigns::CampaignEntry],
dismissed_ids: &std::collections::HashSet<String>,
) -> toml::Value {
let mut merged = self.effective_config_base();
let active = self.resolve_campaigns(&merged, remote_campaigns, dismissed_ids);
self.apply_campaign_overrides(&mut merged, &active);
merged
}
/// Disk campaigns + on-disk dismiss (`campaigns_state.json`); **no remote, no
/// env override**. Named to make the divergence from the shell's remote-aware
/// `load_effective_config` explicit at every call site.
pub fn effective_config_disk_only(&self) -> toml::Value {
self.effective_config_with_campaigns(&[], &load_dismissed_ids_from_home())
}
pub fn has_managed(&self) -> bool {
self.managed.as_table().is_some_and(|t| !t.is_empty())
|| self
.system_managed
.as_table()
.is_some_and(|t| !t.is_empty())
}
pub fn has_system_managed(&self) -> bool {
self.system_managed
.as_table()
.is_some_and(|t| !t.is_empty())
}
}
/// `KIGI_CAMPAIGNS=0` or `[features] campaigns = false` on pre-campaign base.
pub fn campaigns_application_disabled(base_effective: &toml::Value) -> bool {
if crate::env_bool("KIGI_CAMPAIGNS") == Some(false) {
return true;
}
base_effective
.get("features")
.and_then(|f| f.get("campaigns"))
.and_then(|c| c.as_bool())
== Some(false)
}
/// Disk layers only (no remote, no env override). Prefer the shell loader
/// (`kigi_shell::util::config::load_effective_config`) when remote campaigns
/// or `KIGI_CAMPAIGNS_OVERRIDE` must be honored. The name mirrors the
/// [`ConfigLayers::effective_config_disk_only`] method so the divergence from the
/// remote-aware loader is un-ignorable at every call site.
pub fn load_effective_config_disk_only() -> std::io::Result<toml::Value> {
Ok(ConfigLayers::load()?.effective_config_disk_only())
}
/// On-disk campaign dismiss state. Single source of truth for the file's name,
/// location, and JSON shape — the shell's writer reuses these so the read and
/// write sides can't drift.
pub const CAMPAIGNS_STATE_FILE: &str = "campaigns_state.json";
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct CampaignsState {
#[serde(default)]
pub dismissed_ids: Vec<String>,
}
/// Path to `$KIGI_SHARE_DIR/campaigns_state.json` under `home`.
pub fn campaigns_state_path(home: &std::path::Path) -> std::path::PathBuf {
home.join(CAMPAIGNS_STATE_FILE)
}
/// Fail-open dismissed ids from `$KIGI_SHARE_DIR/campaigns_state.json`.
pub fn load_dismissed_ids_from_home() -> std::collections::HashSet<String> {
let Some(home) = crate::user_kigi_home() else {
return std::collections::HashSet::new();
};
let Ok(contents) = std::fs::read_to_string(campaigns_state_path(&home)) else {
return std::collections::HashSet::new();
};
serde_json::from_str::<CampaignsState>(&contents)
.map(|s| s.dismissed_ids.into_iter().collect())
.unwrap_or_default()
}
/// Applies matching `[[version_overrides]]` patches against the running
/// CLI version; strips the section either way. If the installed version
/// can't be parsed (broken `KIGI_TEST_VERSION` in dev), silently strips
/// without applying — keeps the CLI usable on a bad dev override.
pub fn apply_version_overrides_with_registered(value: &mut toml::Value) -> std::io::Result<()> {
match kigi_version::installed_semver() {
Ok(version) => apply_version_overrides(value, &version)
.map_err(|e| std::io::Error::other(e.to_string())),
Err(_) => {
if let Some(table) = value.as_table_mut() {
table.remove(version_overrides::VERSION_OVERRIDES_KEY);
}
Ok(())
}
}
}
/// Recursively merge `overrides` into `base`. Values in `overrides` win.
pub fn deep_merge_toml(base: &mut toml::Value, overrides: &toml::Value) {
if let toml::Value::Table(overrides_table) = overrides
&& let toml::Value::Table(base_table) = base
{
for (key, value) in overrides_table {
if let Some(existing) = base_table.get_mut(key) {
deep_merge_toml(existing, value);
} else {
base_table.insert(key.clone(), value.clone());
}
}
} else {
*base = overrides.clone();
}
}
/// Expand `$VAR` / `${VAR}` in all string values.
pub fn expand_env_vars_in_toml(value: &mut toml::Value) {
match value {
toml::Value::String(s) => {
let expanded = expand_env_vars_in_string(s);
if expanded != *s {
*s = expanded;
}
}
toml::Value::Array(items) => {
for item in items {
expand_env_vars_in_toml(item);
}
}
toml::Value::Table(table) => {
for (_, item) in table.iter_mut() {
expand_env_vars_in_toml(item);
}
}
_ => {}
}
}
/// Expand `$VAR` / `${VAR}` in a single string.
pub fn expand_env_vars_in_string(input: &str) -> String {
let context = |name: &str| std::env::var(name).ok();
shellexpand::env_with_context_no_errors(input, context).into_owned()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn full_layer_precedence_requirements_over_config_over_managed() {
let system_managed: toml::Value =
toml::from_str("[telemetry]\nmode = \"system_managed_value\"\n").unwrap();
let managed: toml::Value =
toml::from_str("[telemetry]\nmode = \"managed_value\"\n").unwrap();
let user: toml::Value = toml::from_str("[telemetry]\nmode = \"user_value\"\n").unwrap();
let user_requirements: toml::Value =
toml::from_str("[telemetry]\nmode = \"user_requirements_value\"\n").unwrap();
let system_requirements: toml::Value =
toml::from_str("[telemetry]\nmode = \"system_requirements_value\"\n").unwrap();
let mut merged = system_managed;
deep_merge_toml(&mut merged, &managed);
deep_merge_toml(&mut merged, &user);
deep_merge_toml(&mut merged, &user_requirements);
deep_merge_toml(&mut merged, &system_requirements);
assert_eq!(
merged["telemetry"]["mode"].as_str(),
Some("system_requirements_value")
);
}
#[test]
fn effective_config_mdm_requirements_win_over_system_and_user() {
// MDM is merged last, so an admin-forced value clamps the effective
// config over both the user config and the system requirements layer.
let layers = ConfigLayers {
user: toml::from_str("[features]\nweb_fetch = true\n").unwrap(),
system_requirements: Some(toml::from_str("[features]\nweb_fetch = true\n").unwrap()),
mdm_requirements: Some(toml::from_str("[features]\nweb_fetch = false\n").unwrap()),
..Default::default()
};
assert_eq!(
layers.effective_config_disk_only()["features"]["web_fetch"].as_bool(),
Some(false),
);
}
#[test]
fn full_precedence_holds_when_values_come_from_version_overrides() {
let cli_version = semver::Version::parse("1.8.0").unwrap();
let mut managed: toml::Value = toml::from_str(
r#"
[[version_overrides]]
minimum_version = "1.0.0"
[version_overrides.telemetry]
mode = "managed_versioned"
"#,
)
.unwrap();
let mut user: toml::Value = toml::from_str(
r#"
[[version_overrides]]
minimum_version = "1.0.0"
[version_overrides.telemetry]
mode = "user_versioned"
"#,
)
.unwrap();
let mut requirements: toml::Value = toml::from_str(
r#"
[[version_overrides]]
minimum_version = "1.0.0"
[version_overrides.telemetry]
mode = "requirements_versioned"
"#,
)
.unwrap();
apply_version_overrides(&mut managed, &cli_version).unwrap();
apply_version_overrides(&mut user, &cli_version).unwrap();
apply_version_overrides(&mut requirements, &cli_version).unwrap();
let mut merged = managed;
deep_merge_toml(&mut merged, &user);
deep_merge_toml(&mut merged, &requirements);
assert_eq!(
merged["telemetry"]["mode"].as_str(),
Some("requirements_versioned")
);
}
/// Direct contract for `deep_merge_toml`: nested tables merge (siblings
/// preserved), arrays replace (not concatenate), missing keys insert.
#[test]
fn deep_merge_toml_table_merge_array_replace_and_insert() {
let mut base: toml::Value = toml::from_str(
r#"
[features.telemetry]
enabled = false
sample_rate = 0.0
[server]
allowed = ["a", "b"]
"#,
)
.unwrap();
let overrides: toml::Value = toml::from_str(
r#"
[features.telemetry]
enabled = true
[server]
allowed = ["c"]
[brand_new]
x = 1
"#,
)
.unwrap();
deep_merge_toml(&mut base, &overrides);
assert_eq!(
base["features"]["telemetry"]["enabled"].as_bool(),
Some(true)
);
assert_eq!(
base["features"]["telemetry"]["sample_rate"].as_float(),
Some(0.0)
);
let arr: Vec<_> = base["server"]["allowed"]
.as_array()
.unwrap()
.iter()
.filter_map(|v| v.as_str())
.collect();
assert_eq!(arr, vec!["c"]);
assert_eq!(base["brand_new"]["x"].as_integer(), Some(1));
}
#[test]
fn user_version_overrides_dont_escape_their_layer() {
let cli_version = semver::Version::parse("1.8.0").unwrap();
let mut user: toml::Value = toml::from_str(
r#"
[[version_overrides]]
minimum_version = "1.0.0"
[version_overrides.telemetry]
mode = "enabled"
"#,
)
.unwrap();
apply_version_overrides(&mut user, &cli_version).unwrap();
assert_eq!(user["telemetry"]["mode"].as_str(), Some("enabled"));
let requirements: toml::Value = toml::from_str(
r#"
[telemetry]
mode = "disabled"
"#,
)
.unwrap();
let mut merged = user;
deep_merge_toml(&mut merged, &requirements);
assert_eq!(merged["telemetry"]["mode"].as_str(), Some("disabled"));
}
#[test]
fn load_user_config_layer_is_empty_without_user_home() {
// No resolvable user home: no user layer, and crucially no
// cwd-relative .kigi read.
let v = load_user_config_layer(None, "config.toml").unwrap();
assert_eq!(v.as_table().map(|t| t.is_empty()), Some(true));
}
#[test]
fn load_user_config_layer_reads_file_when_home_present() {
use std::io::Write;
let dir = std::env::temp_dir().join(format!("grok-load-layer-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let mut f = std::fs::File::create(dir.join("config.toml")).unwrap();
writeln!(f, "[telemetry]\nmode = \"from_file\"\n").unwrap();
let v = load_user_config_layer(Some(&dir), "config.toml").unwrap();
assert_eq!(v["telemetry"]["mode"].as_str(), Some("from_file"));
let _ = std::fs::remove_dir_all(&dir);
}
/// The returned error keeps the parser's kind + location but never the source
/// snippet, which can carry a secret and would reach a client caller.
#[test]
fn parse_error_keeps_kind_but_not_snippet() {
let dir = std::env::temp_dir().join(format!("grok-toml-leak-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("bad.toml");
// Duplicate key: the message names the key; the secret-bearing source line is only in Display.
std::fs::write(
&path,
"api_key = \"xai-secretmustnotleak\"\napi_key = \"xai-secretmustnotleak2\"\n",
)
.unwrap();
let msg = load_toml_file(&path).unwrap_err().to_string();
assert!(
msg.contains("TOML parse error at line 2"),
"want location: {msg}"
);
assert!(msg.contains("duplicate key"), "want parser kind: {msg}");
assert!(
!msg.contains("xai-secretmustnotleak"),
"leaked the secret value: {msg}"
);
assert!(
!msg.contains('|') && !msg.contains('^'),
"leaked the source snippet/caret: {msg}"
);
let _ = std::fs::remove_dir_all(&dir);
}
/// `KIGI_CAMPAIGNS=0` disables campaign application regardless of config.
/// `KIGI_CAMPAIGNS` is process-global, so this test serializes itself with a
/// module-local mutex and save/restores the prior value. (This crate has no
/// `serial_test` dev-dep and no other test reads this var, so a local guard
/// is sufficient.)
#[test]
fn kill_switch_env_var_disables() {
static ENV_GUARD: std::sync::Mutex<()> = std::sync::Mutex::new(());
let _g = ENV_GUARD.lock().unwrap_or_else(|p| p.into_inner());
let prior = std::env::var_os("KIGI_CAMPAIGNS");
let empty = toml::Value::Table(Default::default());
// SAFETY: ENV_GUARD serializes this against itself; no other test in the
// crate mutates or reads KIGI_CAMPAIGNS concurrently.
unsafe { std::env::set_var("KIGI_CAMPAIGNS", "0") };
assert!(campaigns_application_disabled(&empty));
unsafe { std::env::remove_var("KIGI_CAMPAIGNS") };
assert!(!campaigns_application_disabled(&empty));
match prior {
Some(v) => unsafe { std::env::set_var("KIGI_CAMPAIGNS", v) },
None => unsafe { std::env::remove_var("KIGI_CAMPAIGNS") },
}
}
}