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
@@ -0,0 +1,246 @@
//! The deployment-config fetch/response contract: the credential source and its
//! errors, response parsing, envelope picking, fetched-envelope verification, and
//! the apply outcome the sync orchestration consumes.
use kigi_config::signed_policy::now_unix;
use serde::Deserialize;
/// Which credential a config fetch used — tailors error messages and the
/// post-fetch confirmation (team vs deployment).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum ManagedConfigSource {
DeploymentKey,
TeamOauth,
}
impl ManagedConfigSource {
pub(super) fn is_team(self) -> bool {
matches!(self, Self::TeamOauth)
}
/// The 401/403 error tailored to the credential (don't tell a team user to
/// check `KIGI_DEPLOYMENT_KEY`).
pub(super) fn auth_rejected_error(self) -> ManagedConfigError {
if self.is_team() {
ManagedConfigError::TeamAuthRejected
} else {
ManagedConfigError::DeploymentKeyRejected
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum ManagedConfigError {
#[error("Can't reach the server. Check your network connection and try again.\n ({0})")]
Network(String),
#[error(
"The connection to the server was interrupted or timed out before completing. This is usually temporary; please try again.\n ({0})"
)]
ConnectionInterrupted(String),
#[error(
"The deployment key was rejected. Confirm that KIGI_DEPLOYMENT_KEY is set correctly and hasn't expired."
)]
DeploymentKeyRejected,
#[error(
"Your team sign-in was rejected. It may have expired or lack access. Run `grok login` to sign in again."
)]
TeamAuthRejected,
#[error("The server returned an unexpected error (HTTP {status}). Try again in a few minutes.")]
ServerError { status: u16 },
#[error("The server returned an unexpected response.\n ({0})")]
InvalidResponse(String),
#[error(
"The configuration request couldn't be completed due to a client-side error (not a server response). This is unexpected; please report it if it persists.\n ({0})"
)]
RequestFailed(String),
#[error(
"The server's response could not be verified as authentic managed policy, so nothing was installed. Try again; if this persists, contact your administrator."
)]
SignatureRejected,
#[error(
"Can't save the configuration to ~/.kigi. Make sure the directory exists and is writable.\n ({0})"
)]
DiskWrite(#[from] std::io::Error),
}
impl ManagedConfigError {
/// Transient failure (network / connection interruption / server 5xx) where retrying may succeed.
pub fn is_retryable(&self) -> bool {
matches!(
self,
Self::Network(_) | Self::ConnectionInterrupted(_) | Self::ServerError { .. }
)
}
/// Auth/eligibility rejection (no access or expired session) — not fixable by retrying.
pub fn is_auth_rejection(&self) -> bool {
matches!(self, Self::TeamAuthRejected | Self::DeploymentKeyRejected)
}
}
#[derive(Deserialize, Default)]
pub(super) struct ManagedConfigResponse {
#[serde(default)]
pub(super) deployment_id: Option<String>,
#[serde(default)]
pub(super) team_id: Option<String>,
pub(super) managed_config: Option<String>,
pub(super) requirements: Option<String>,
/// The signed envelopes (additive; absent from old servers), primary first —
/// a rollover server dual-signs, each payload signed by ITS OWN key. The
/// signed payload's policy is the trusted copy when verification is on.
#[serde(default)]
pub(super) signatures: Option<Vec<kigi_config::signed_policy::SignatureEnvelope>>,
}
impl ManagedConfigResponse {
pub(super) fn config_exists(&self) -> bool {
self.deployment_id.is_some() || self.team_id.is_some()
}
/// The signed envelope to verify, when the server included any: the first
/// `signatures` entry whose key_id is in the embedded trusted set, else the
/// first entry (so verification reports the real UnknownKeyId failure). The
/// outer key_id only PICKS — verification re-selects the key from the signed bytes.
pub(super) fn signature_sidecar(
&self,
) -> Option<kigi_config::signed_policy::SignatureEnvelope> {
self.signature_sidecar_with(kigi_config::signed_policy::embedded_key_id_trusted)
}
/// Predicate-injected core of [`Self::signature_sidecar`] so tests can pick
/// without a compiled-in key set.
fn signature_sidecar_with(
&self,
key_id_trusted: impl Fn(&str) -> bool,
) -> Option<kigi_config::signed_policy::SignatureEnvelope> {
let envelopes = self.signatures.as_deref()?;
envelopes
.iter()
.find(|e| key_id_trusted(&e.key_id))
.or_else(|| envelopes.first())
.cloned()
}
/// Non-empty served content, recorded in the marker so staleness can later detect a deleted file.
pub(super) fn has_managed_config(&self) -> bool {
self.managed_config
.as_deref()
.is_some_and(|s| !s.is_empty())
}
pub(super) fn has_requirements(&self) -> bool {
self.requirements.as_deref().is_some_and(|s| !s.is_empty())
}
/// The served opt-in (`fail_closed`), read from the payload not disk, so it's authoritative even when
/// the on-disk apply is skipped under lock contention.
pub(super) fn requirements_fail_closed(&self) -> bool {
self.requirements
.as_deref()
.is_some_and(crate::config::fail_closed_flag_from_str)
}
}
/// Result of [`apply_fetched`].
pub(super) enum ApplyOutcome {
/// Persisted the policy (`wrote` = at least one artifact written or removed), or
/// skipped under lock contention / a vanished credential (`wrote` = false); the
/// marker should be recorded either way. `signed_deployment_id` is the VERIFIED
/// payload's `deployment_id` (`None` when unsigned) — stronger than the body's,
/// which is omitted on the signed-empty response.
Applied {
wrote: bool,
signed_deployment_id: Option<String>,
},
/// Verification is active and the envelope did not verify — nothing was persisted.
/// The marker must NOT be recorded: it would claim a body that was never written.
SignatureRejected,
}
impl ApplyOutcome {
pub(super) fn wrote(&self) -> bool {
matches!(self, Self::Applied { wrote: true, .. })
}
pub(super) fn signature_rejected(&self) -> bool {
matches!(self, Self::SignatureRejected)
}
pub(super) fn signed_deployment_id(&self) -> Option<&str> {
match self {
Self::Applied {
signed_deployment_id,
..
} => signed_deployment_id.as_deref(),
Self::SignatureRejected => None,
}
}
}
/// A fetched envelope that passed verification: the sidecar to persist, plus its
/// parsed (now-trusted) payload.
pub(super) struct VerifiedEnvelope {
pub(super) sidecar: kigi_config::signed_policy::SignatureEnvelope,
pub(super) payload: kigi_config::signed_policy::SignedPayload,
}
/// Verify the signed envelope without persisting anything. The legacy body fields must
/// equal the signed copy, so what lands on disk is exactly what was signed (and the
/// load-time gate can re-verify it). The error is a plain message: the caller only logs it.
pub(super) fn verify_signed_envelope(
body: &ManagedConfigResponse,
active_team_id: Option<&str>,
) -> Result<VerifiedEnvelope, String> {
use kigi_config::signed_policy;
let sidecar = body.signature_sidecar().ok_or_else(|| {
"managed policy is required but the server returned no signature".to_owned()
})?;
let payload = signed_policy::verify_fetched(&sidecar, active_team_id, now_unix())
.map_err(|e| e.to_string())?;
if body.managed_config != payload.managed_config || body.requirements != payload.requirements {
return Err("served policy does not match the signed payload".to_owned());
}
Ok(VerifiedEnvelope { sidecar, payload })
}
#[cfg(test)]
mod tests {
use super::*;
/// Picking: the first trusted-key_id entry wins; no trusted entry → the first entry
/// (picking must not invent absence); no array (old/unsigned server) → None.
#[test]
fn signature_sidecar_picks_trusted_envelope_then_falls_back() {
use kigi_config::signed_policy::SignatureEnvelope;
let envelope = |kid: &str| SignatureEnvelope {
signed_payload: format!("payload-{kid}"),
signature: format!("sig-{kid}"),
key_id: kid.to_owned(),
};
let body = ManagedConfigResponse {
signatures: Some(vec![envelope("v1"), envelope("v2")]),
..Default::default()
};
// A rotated client trusting only v2 picks the v2 envelope from the array.
let picked = body.signature_sidecar_with(|id| id == "v2").unwrap();
assert_eq!(picked.key_id, "v2");
assert_eq!(picked.signed_payload, "payload-v2");
// Trusting v1 picks the primary entry (first in the array).
let picked = body.signature_sidecar_with(|id| id == "v1").unwrap();
assert_eq!(picked.key_id, "v1");
// No trusted id → the first entry, so verification reports UnknownKeyId.
let picked = body.signature_sidecar_with(|_| false).unwrap();
assert_eq!(picked.key_id, "v1");
// Nothing signed at all → None.
assert!(
ManagedConfigResponse::default()
.signature_sidecar_with(|_| true)
.is_none()
);
}
}
@@ -0,0 +1,323 @@
use super::*;
/// Fail closed only for a managed principal AND compromised policy; every other combination proceeds.
#[test]
fn gate_blocks_only_managed_principal_with_compromised_policy() {
// The one blocking case.
assert!(
managed_policy_gate_decision(true, true).is_err(),
"managed principal + compromised policy must fail closed"
);
// Policy intact / opted-out → proceed even for a managed principal.
assert!(managed_policy_gate_decision(true, false).is_ok());
// No managed principal → nothing to enforce.
assert!(managed_policy_gate_decision(false, true).is_ok());
assert!(managed_policy_gate_decision(false, false).is_ok());
}
/// Writes both artifacts and overwrites in place on re-fetch.
#[test]
fn apply_writes_and_overwrites_artifacts() {
let dir = tempfile::tempdir().unwrap();
let home = dir.path();
std::fs::write(home.join("managed_config.toml"), "[cli]\nold = true\n").unwrap();
let body = ManagedConfigResponse {
deployment_id: None,
team_id: None,
managed_config: Some("[cli]\ntheme = \"dark\"\n".into()),
requirements: Some("[features]\nweb_fetch = false\n".into()),
..Default::default()
};
assert!(apply_managed_config(home, &body).unwrap());
assert_eq!(
std::fs::read_to_string(home.join("managed_config.toml")).unwrap(),
"[cli]\ntheme = \"dark\"\n",
"managed_config is overwritten with the served content"
);
assert_eq!(
std::fs::read_to_string(home.join("requirements.toml")).unwrap(),
"[features]\nweb_fetch = false\n"
);
}
/// An artifact the response no longer serves (absent or empty) is REMOVED — a withdrawn
/// policy must stop enforcing, and a leftover would trip the signed absence check.
#[test]
fn apply_removes_artifact_the_server_no_longer_serves() {
let dir = tempfile::tempdir().unwrap();
let home = dir.path();
std::fs::write(home.join("requirements.toml"), "[features]\n").unwrap();
// Response carries managed_config but NOT requirements.
let body = ManagedConfigResponse {
deployment_id: None,
team_id: None,
managed_config: Some("[cli]\ntheme = \"dark\"\n".into()),
requirements: None,
..Default::default()
};
assert!(apply_managed_config(home, &body).unwrap());
assert!(home.join("managed_config.toml").exists());
assert!(
!home.join("requirements.toml").exists(),
"an artifact the server no longer serves is removed"
);
// EMPTY served content means the same thing as absent: remove.
let withdrawn = ManagedConfigResponse {
deployment_id: None,
team_id: None,
managed_config: Some(String::new()),
requirements: None,
..Default::default()
};
assert!(apply_managed_config(home, &withdrawn).unwrap());
assert!(
!home.join("managed_config.toml").exists(),
"empty served content converges to absence"
);
// Converged state: another empty apply changes nothing.
assert!(!apply_managed_config(home, &withdrawn).unwrap());
}
/// Partial-write robustness: if one artifact lands and the other write
/// fails, the error surfaces but the artifact that succeeded is kept.
#[cfg(unix)]
#[test]
fn apply_partial_write_failure_keeps_written_artifact() {
use std::os::unix::fs::PermissionsExt;
let dir = tempfile::tempdir().unwrap();
let home = dir.path();
// Force the requirements write to fail: a squatting dir whose child can't be
// unlinked (no write bit on the dir), so even the dir-squat clearing fails
// and the rename onto the dir fails after it.
let req = home.join("requirements.toml");
std::fs::create_dir(&req).unwrap();
std::fs::write(req.join("pin"), "x").unwrap();
std::fs::set_permissions(&req, std::fs::Permissions::from_mode(0o500)).unwrap();
if std::fs::remove_dir_all(&req).is_ok() {
eprintln!("skipping: permissions not enforced (running as root?)");
return;
}
let body = ManagedConfigResponse {
deployment_id: None,
team_id: None,
managed_config: Some("[cli]\ninstaller = \"internal\"\n".into()),
requirements: Some("[features]\nweb_fetch = false\n".into()),
..Default::default()
};
let result = apply_managed_config(home, &body);
assert!(result.is_err(), "requirements write must fail");
assert!(
home.join("managed_config.toml").exists(),
"the artifact that wrote successfully must be kept"
);
// Tidy so the tempdir can be cleaned up.
let _ = std::fs::set_permissions(&req, std::fs::Permissions::from_mode(0o700));
}
/// The apply clears a squatting directory on both the overwrite and removal branches,
/// so a dir-squat can't permanently block convergence.
#[test]
fn apply_converges_over_a_squatting_directory() {
let dir = tempfile::tempdir().unwrap();
let home = dir.path();
std::fs::create_dir(home.join("requirements.toml")).unwrap();
std::fs::write(home.join("requirements.toml").join("junk"), "x").unwrap();
std::fs::create_dir(home.join("managed_config.toml")).unwrap();
// Overwrite branch clears the dir and writes; removal branch clears the dir.
let body = ManagedConfigResponse {
deployment_id: None,
team_id: None,
managed_config: Some("[cli]\ntheme = \"dark\"\n".into()),
requirements: None,
..Default::default()
};
assert!(apply_managed_config(home, &body).unwrap());
assert_eq!(
std::fs::read_to_string(home.join("managed_config.toml")).unwrap(),
"[cli]\ntheme = \"dark\"\n",
"the squatting directory is replaced by the served file"
);
assert!(
!home.join("requirements.toml").exists(),
"the squatting directory in a served-absent slot is removed"
);
}
/// The purge primitive (shared by logout and the identity-change purge) is best-effort over a
/// PARTIAL install: present artifacts are removed, absent ones are tolerated (no panic, no
/// error), and a directory squatting an artifact path is removed too.
#[test]
fn remove_managed_config_files_tolerates_partial_existence() {
let dir = tempfile::tempdir().unwrap();
let home = dir.path();
// Only two of the four artifacts exist; one of them is a squatting DIRECTORY.
std::fs::write(home.join("requirements.toml"), "[features]\n").unwrap();
std::fs::create_dir(home.join("managed_config.toml")).unwrap();
std::fs::write(home.join("managed_config.toml").join("junk"), "x").unwrap();
remove_managed_config_files(home);
for f in [
"requirements.toml",
"managed_config.toml",
"managed_config_cache.json",
"managed_config.sig.json",
] {
assert!(
!home.join(f).exists(),
"{f} must be gone after the purge (absent ones tolerated, dir squat removed)"
);
}
}
/// The transport-interruption variant must be retryable (so the loop escapes a
/// poisoned connection) and must not be mistaken for an auth rejection.
#[test]
fn connection_interrupted_is_retryable_not_auth() {
let e = ManagedConfigError::ConnectionInterrupted("closed".into());
assert!(e.is_retryable(), "a transient interruption must be retried");
assert!(
!e.is_auth_rejection(),
"a transport error is not an auth rejection"
);
}
/// Each `TransportFailureKind` maps to the right `ManagedConfigError` with the right retryability:
/// a `Permanent` (builder/redirect) failure is a client-side defect, so it maps to the terminal
/// `RequestFailed`, not the server-blaming `InvalidResponse`, and must never be retried.
#[test]
fn transport_failure_maps_to_managed_config_error() {
use crate::http::{TransportFailure, TransportFailureKind};
let unreachable = map_transport_failure(TransportFailure {
kind: TransportFailureKind::Unreachable,
detail: "connection refused".into(),
});
assert!(matches!(unreachable, ManagedConfigError::Network(_)));
assert!(
unreachable.is_retryable(),
"an unreachable server is retried"
);
assert!(!unreachable.is_auth_rejection());
let interrupted = map_transport_failure(TransportFailure {
kind: TransportFailureKind::Interrupted,
detail: "connection closed before message completed".into(),
});
assert!(matches!(
interrupted,
ManagedConfigError::ConnectionInterrupted(_)
));
assert!(
interrupted.is_retryable(),
"an in-flight interruption is retried"
);
let permanent = map_transport_failure(TransportFailure {
kind: TransportFailureKind::Permanent,
detail: "too many redirects".into(),
});
assert!(
matches!(permanent, ManagedConfigError::RequestFailed(_)),
"a client-side defect maps to RequestFailed, not InvalidResponse"
);
assert!(
!permanent.is_retryable(),
"a client-side defect is terminal and must not be retried"
);
assert!(!permanent.is_auth_rejection());
}
/// `send_with_retry_escaping_pool` combinator behavior with a counting op (no network):
/// retryable errors retry up to `max_attempts`, non-retryable fails fast, success
/// short-circuits, backoff awaited once per retry. The fresh-client swap on the final
/// attempt needs a real degraded upstream and stays covered by the headless/e2e pass.
#[tokio::test]
async fn send_with_retry_escaping_pool_combinator_behavior() {
use std::sync::atomic::{AtomicU32, Ordering};
// (a) all-retryable: op runs max_attempts times, backoff awaited max_attempts-1 times, last Err returned.
let op_calls = AtomicU32::new(0);
let backoffs = AtomicU32::new(0);
let exhausted: Result<(), u32> = crate::http::send_with_retry_escaping_pool(
|_client| {
let n = op_calls.fetch_add(1, Ordering::SeqCst);
async move { Err(n) }
},
3,
|_e: &u32| true,
|_attempt| {
backoffs.fetch_add(1, Ordering::SeqCst);
std::future::ready(())
},
)
.await;
assert_eq!(exhausted, Err(2), "returns the last attempt's error");
assert_eq!(
op_calls.load(Ordering::SeqCst),
3,
"op runs max_attempts times"
);
assert_eq!(
backoffs.load(Ordering::SeqCst),
2,
"backoff awaited max_attempts-1 times"
);
// (b) non-retryable: fail fast after one op call, no backoff.
let op_calls = AtomicU32::new(0);
let backoffs = AtomicU32::new(0);
let fast: Result<(), u32> = crate::http::send_with_retry_escaping_pool(
|_client| {
op_calls.fetch_add(1, Ordering::SeqCst);
async { Err(7) }
},
5,
|_e: &u32| false,
|_attempt| {
backoffs.fetch_add(1, Ordering::SeqCst);
std::future::ready(())
},
)
.await;
assert_eq!(fast, Err(7));
assert_eq!(
op_calls.load(Ordering::SeqCst),
1,
"a non-retryable error fails fast"
);
assert_eq!(
backoffs.load(Ordering::SeqCst),
0,
"no backoff on a fast failure"
);
// (c) success short-circuits: fail once (retryable), then succeed on the 2nd attempt.
let op_calls = AtomicU32::new(0);
let ok: Result<u32, u32> = crate::http::send_with_retry_escaping_pool(
|_client| {
let n = op_calls.fetch_add(1, Ordering::SeqCst);
let outcome: Result<u32, u32> = if n == 0 { Err(1) } else { Ok(42) };
async move { outcome }
},
5,
|_e: &u32| true,
|_attempt| std::future::ready(()),
)
.await;
assert_eq!(ok, Ok(42));
assert_eq!(
op_calls.load(Ordering::SeqCst),
2,
"stops at the first success"
);
}