#[path = "doctor/durable_grants.rs"]
mod durable_grants;
use newt_core::dgx::{DgxConfig, EndpointKind};
use newt_core::ocap::SecurityReport;
use newt_core::Config;
use newt_inference::local::LocalOllamaBackend;
use std::path::Path;
fn security_posture_lines(report: &SecurityReport) -> Vec<String> {
let mut lines = Vec::with_capacity(report.summary_lines().len() + 1);
lines.push(format!("platform: {}", report.platform));
lines.extend(report.summary_lines());
lines
}
pub async fn run(config_path: Option<&Path>, fix: bool) -> anyhow::Result<()> {
println!("newt doctor — checking backends\n");
println!("Push tool paths:");
for line in newt_core::git_staging::tool_diagnostics::diagnose_tool_paths().lines {
println!(" {line}");
}
let config = match config_path {
Some(p) => crate::migration_notices::read(|report| Config::load(p, report)),
None => crate::migration_notices::read(|report| Config::resolve(report)),
};
let config = match config {
Ok(c) => Some(c),
Err(e) => {
println!("FINDING: config did not resolve — {e}\n");
None
}
};
diagnose_backend_dropins(fix, config.is_some()).await;
durable_grants::diagnose(config.as_ref(), fix);
let Some(config) = config else {
println!(
"\n(backend/provider/DGX/MCP checks skipped — config did not resolve; \
see the finding above)"
);
return Ok(());
};
println!("\nConfigured backends:");
for backend in &config.backends {
let status = probe_configured_backend(backend).await;
println!(
" {} ({}, {}) — {status}",
backend.name,
backend.endpoint,
backend.kind_label()
);
if let Some(line) = token_status_line(
backend.api_key_env.as_deref(),
backend.api_key_file.as_deref(),
) {
println!(" token: {line}");
}
}
println!("\nConfigured providers:");
if config.providers.is_empty() {
println!(" (none)");
}
for provider in &config.providers {
let status = probe_provider(&provider.command);
println!(
" {} (command: {}) — {status}",
provider.name, provider.command
);
}
println!("\nDGX nodes:");
match &config.dgx {
None => println!(" (none configured)"),
Some(dgx) => probe_dgx(dgx).await,
}
println!("\nEndpoint discovery:");
match LocalOllamaBackend::discover("default").await {
Ok(backend) => println!(" Ollama: reachable at {}", backend.endpoint()),
Err(e) => println!(" Ollama: {e}"),
}
println!("\nShell engine (OCAP):");
let (backend, l3_active) = newt_core::ocap_l3_backend();
let worker_available = newt_core::ShellEngine::Brush.validate_available().is_ok();
match config.shell.as_ref().and_then(|s| s.engine) {
Some(engine) => {
println!(" configured engine (L2): {engine} (explicit [shell] engine)");
if let Err(reason) = engine.validate_available() {
println!(" startup refusal: {reason}");
}
}
None => {
let resolved = newt_core::resolved_confined_default();
let why = if resolved == newt_core::ShellEngine::Brush {
"native fence and authenticated worker transport available"
} else if !worker_available {
"authenticated worker transport unavailable — full shell grammar unavailable"
} else {
"no per-run kernel fence here — safe-subset refuses dynamic constructs"
};
println!(
" confined default (L2): {resolved} — L3-gated, resolved per run_command ({why})"
);
}
}
println!(" · safe-subset — refuses $(...)/dynamic constructs (portable default)");
println!(" · host — real /bin/sh (full grammar; restricted grants require native confinement)");
println!("{}", brush_engine_description(worker_available));
println!(
" full-access default: {}",
newt_core::full_access_default_engine()
);
println!(" override per-run: --shell-engine <safe-subset|host|brush>");
println!(
" L3 kernel jail (this platform): {backend} — {}",
if l3_active {
"available"
} else {
"NOT available → a restricted fs grant is refused when its kernel floor cannot be met"
}
);
println!(
" agent-bridle attenuates your full ambient authority into structural OCAP grants; \
--full-access temporarily lifts them."
);
println!("\nAchieved OCAP posture (per guarantee):");
for line in security_posture_lines(&newt_core::ocap::SecurityReport::current()) {
println!(" {line}");
}
println!("\nMCP servers (newt config + Claude Code config):");
let home = std::env::var_os("HOME").map(std::path::PathBuf::from);
let workspace = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
let mcp_toml = newt_core::Config::user_config_dir().map(|d| d.join("mcp.toml"));
let servers = newt_core::mcp::discover(
&config.mcp_servers,
mcp_toml.as_deref(),
home.as_deref(),
&workspace,
);
if servers.is_empty() {
println!(" (none discovered)");
}
let mcp_caveats = config.mcp_probe_caveats(&workspace);
for s in &servers {
match s.transport {
newt_core::mcp::TransportKind::Stdio => {
let admitted = match newt_core::mcp::admit(s) {
Ok(a) => a,
Err(denied) => {
println!(" · {} — not admitted: {denied}", s.name);
continue;
}
};
match newt_mcp_client::connect_stdio(&admitted, &mcp_caveats).await {
Ok(connected) => {
let names: Vec<&str> =
connected.tools.iter().map(|t| t.name.as_str()).collect();
let list = if names.is_empty() {
"(none)".to_string()
} else {
names.join(", ")
};
println!(" {} [stdio] — OK, {} tool(s): {list}", s.name, names.len());
}
Err(e) => println!(" {} [stdio] — ERROR: {e:#}", s.name),
}
}
newt_core::mcp::TransportKind::Sse | newt_core::mcp::TransportKind::Http => {
let kind = if matches!(s.transport, newt_core::mcp::TransportKind::Sse) {
"sse"
} else {
"http"
};
let url = s.url.clone().unwrap_or_default();
println!(
" {} [{kind}] — {url} (skipped: only stdio is supported in this build)",
s.name
);
}
}
}
Ok(())
}
fn backend_dropin_dirs() -> Vec<std::path::PathBuf> {
let mut dirs = Vec::new();
if let Some(dir) = Config::user_config_dir() {
dirs.push(dir.join("backends"));
}
if let Some(proj) = Config::project_config_path() {
if let Some(parent) = proj.parent() {
let dir = parent.join("backends");
if !dirs.contains(&dir) {
dirs.push(dir);
}
}
}
dirs
}
async fn diagnose_backend_dropins(fix: bool, config_resolved: bool) {
println!("Backend drop-ins:");
let mut seen_stems: std::collections::HashMap<String, std::path::PathBuf> =
std::collections::HashMap::new();
let mut any = false;
for dir in backend_dropin_dirs() {
let Ok(entries) = std::fs::read_dir(&dir) else {
continue;
};
let mut paths: Vec<std::path::PathBuf> = entries
.flatten()
.map(|e| e.path())
.filter(|p| p.extension().is_some_and(|x| x == "toml"))
.collect();
paths.sort();
for path in paths {
any = true;
if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
if let Some(shadowed) = seen_stems.get(stem) {
println!(
" {} — SHADOWED by {} (same name, lower precedence; never read)",
shadowed.display(),
path.display()
);
} else {
seen_stems.insert(stem.to_string(), path.clone());
}
}
let text = match std::fs::read_to_string(&path) {
Ok(t) => t,
Err(e) => {
println!(" {} — FINDING: cannot read ({e})", path.display());
continue;
}
};
match newt_core::classify_backend_dropin(&text) {
Ok(ownership) => {
let label = match ownership {
newt_core::DropinOwnership::Operator => "operator",
newt_core::DropinOwnership::Probe => "probe",
_ => "unknown ownership (newer variant than this build knows)",
};
println!(" {} — OK ({label})", path.display());
if !config_resolved {
if let Ok(backend) = toml::from_str::<newt_core::BackendConfig>(&text) {
if !backend.endpoint.is_empty()
&& backend.kind != Some(newt_core::BackendKind::Embedded)
{
let status = probe_configured_backend(&backend).await;
println!(" live: {status}");
}
}
}
}
Err(reason) => {
println!(" {} — FINDING: {reason}", path.display());
if fix {
repair_ambiguous_dropin(&path, &text).await;
}
}
}
}
}
if !any {
println!(" (none)");
}
}
async fn repair_ambiguous_dropin(path: &Path, text: &str) {
use std::io::IsTerminal as _;
if !std::io::stdin().is_terminal() {
println!(" --fix needs a terminal to choose — refusing to guess, nothing changed");
return;
}
let menu = newt_core::interaction_form::menu(
format!("{}: operator record or probe residue?", path.display()),
"carries the old newt-adopt probe marker plus operator-looking fields — \
ambiguous, so nothing changes until you choose",
&[
(
"o",
"claim as operator config (tag record = \"operator_v1\", keep every field)",
),
(
"d",
"discard as probe residue (delete the file; newt re-probes on next use)",
),
],
);
let window = newt_core::tty::Terminal::suspend_for_prompt(
newt_core::tty::TerminalTaker::PlainCliConfirm,
);
let picked = newt_core::interaction_terminal::resolve_on_terminal(&window, &menu);
match picked.as_ref().map(|id| id.as_str()) {
Some("o") => match newt_core::claim_backend_dropin_as_operator(text) {
Ok(claimed) => {
match newt_core::atomic_fs::ResolvedPath::resolve(path)
.and_then(|dest| dest.atomic_write(claimed.as_bytes()))
{
Ok(()) => println!(" fixed: claimed as operator configuration"),
Err(e) => println!(" fix failed: {e:#}"),
}
}
Err(e) => println!(" fix failed: {e}"),
},
Some("d") => match std::fs::remove_file(path) {
Ok(()) => println!(" fixed: deleted (probe residue) — newt re-probes on next use"),
Err(e) => println!(" fix failed: {e}"),
},
_ => println!(" no repair chosen — file left as-is"),
}
}
async fn probe_dgx(dgx: &DgxConfig) {
let active_node_name = dgx.active_node.as_deref();
let active_endpoint = dgx.active_endpoint;
let active_model = dgx.active_model.as_deref().unwrap_or("(none)");
if dgx.nodes.is_empty() {
println!(" (no nodes — using env overrides only)");
}
for node in &dgx.nodes {
let is_active_node = active_node_name.map_or(dgx.nodes.len() == 1, |n| n == node.name);
let node_marker = if is_active_node { " [active node]" } else { "" };
println!(" {}{node_marker}", node.name);
for kind in EndpointKind::ALL {
let Some(url) = node.endpoint(kind) else {
continue;
};
let is_active_ep = is_active_node && kind == active_endpoint;
let active_marker = if is_active_ep { " *" } else { "" };
let status = if kind.is_openai_compatible() {
probe_vllm(url).await
} else {
probe_backend(url).await
};
println!(" {kind} ({url}){active_marker} — {status}");
}
}
match dgx.resolve_endpoint() {
Ok(url) => println!(" Active: {active_model} @ {active_endpoint} → {url}"),
Err(e) => println!(" Active endpoint: unresolved ({e})"),
}
}
async fn probe_configured_backend(backend: &newt_core::config::BackendConfig) -> String {
if backend.kind == Some(newt_core::config::BackendKind::Embedded) {
return "in-process (embedded — no endpoint to probe)".to_string();
}
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(2))
.build()
.unwrap();
let api_key = backend.resolve_api_key();
if backend.needs_kind_probe() {
return match newt_core::backend_probe::detect_endpoint(
&client,
&backend.endpoint,
api_key.as_deref(),
)
.await
{
Ok(probe) => format!(
"OK (detected {}, {} model(s) served)",
probe.kind.label(),
probe.models.len()
),
Err(e) if e.to_string().starts_with("HTTP ") => e.to_string(),
Err(e) => format!("unreachable: {e}"),
};
}
let kind = backend.kind.expect("needs_kind_probe was false");
match newt_core::backend_probe::api_for(kind)
.list_models(&client, &backend.endpoint, api_key.as_deref())
.await
{
Ok(models) => format!("OK ({} model(s) served)", models.len()),
Err(e) if e.to_string().starts_with("HTTP ") => e.to_string(),
Err(e) => format!("unreachable: {e}"),
}
}
fn token_status_line(api_key_env: Option<&str>, api_key_file: Option<&str>) -> Option<String> {
use newt_core::secrets::TokenStatus;
match newt_core::secrets::token_status(api_key_env, api_key_file) {
TokenStatus::Unset => None,
TokenStatus::FromEnv { var } => Some(format!("from env ${var}")),
TokenStatus::PlaintextFile { path } => Some(format!(
"plaintext file {} (re-run `newt setup` to store it encrypted)",
path.display()
)),
TokenStatus::EncryptedUnlocked { path } => {
Some(format!("encrypted (unlocked) — {}", path.display()))
}
TokenStatus::EncryptedLocked { path, reason } => Some(format!(
"encrypted (LOCKED) — {} · {reason}",
path.display()
)),
TokenStatus::MissingFile { path } => Some(format!("file missing — {}", path.display())),
}
}
async fn probe_vllm(endpoint: &str) -> String {
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(2))
.build()
.unwrap();
let url = format!("{}/v1/models", endpoint.trim_end_matches('/'));
match client.get(&url).send().await {
Ok(resp) if resp.status().is_success() => "OK".to_string(),
Ok(resp) => format!("HTTP {}", resp.status()),
Err(e) => format!("unreachable: {e}"),
}
}
async fn probe_backend(endpoint: &str) -> String {
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(2))
.build()
.unwrap();
let url = format!("{}/api/tags", endpoint.trim_end_matches('/'));
match client.get(&url).send().await {
Ok(resp) if resp.status().is_success() => "OK".to_string(),
Ok(resp) => format!("HTTP {}", resp.status()),
Err(e) => format!("unreachable: {e}"),
}
}
fn probe_provider(command: &str) -> &'static str {
let status = std::process::Command::new(command)
.arg("--version")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status();
match status {
Ok(s) if s.success() => "found on PATH",
Ok(_) => "found but exited with error",
Err(_) => "not found on PATH",
}
}
pub fn sign_ocap() -> anyhow::Result<i32> {
use newt_core::ocap_store::{self, PolicyFile, Verdict};
let Some(config_path) = newt_core::Config::user_config_path() else {
anyhow::bail!("cannot resolve the user config directory (~/.newt)");
};
let (destination, _lock) = ocap_store::lock_approve_file(&config_path)?;
let approve_path = destination.as_path().to_path_buf();
let text = match std::fs::read_to_string(&approve_path) {
Ok(t) => t,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
println!("nothing to sign: {} does not exist", approve_path.display());
return Ok(0);
}
Err(e) => anyhow::bail!("cannot read {}: {e}", approve_path.display()),
};
let mut file = PolicyFile::parse(&text).map_err(|e| anyhow::anyhow!(e))?;
let key_path = newt_identity::default_key_path()?;
let user = newt_identity::load_or_generate(&key_path)?;
let (signed, refused) = ocap_store::sign_approves(
&mut file,
newt_tui::ocap_high_danger_predicate(),
|payload| user.sign(payload).to_bytes(),
);
destination
.atomic_write(file.to_toml().map_err(|e| anyhow::anyhow!(e))?.as_bytes())
.map_err(|e| anyhow::anyhow!("cannot write {}: {e}", approve_path.display()))?;
println!(
"blessed {}: {signed} entr{} signed with the root key ({})",
approve_path.display(),
if signed == 1 { "y" } else { "ies" },
key_path.display()
);
for r in &refused {
println!(" REFUSED: {r}");
}
let (set, warnings) = ocap_store::load_store(&config_path, Some(user.public().as_bytes()));
for w in &warnings {
println!(" load warning: {w}");
}
let live = set
.files
.get(&Verdict::Approve)
.map(|f| f.exec.len() + f.fs.len() + f.net.len())
.unwrap_or(0);
println!("verified: {live} durable grant(s) live at load");
Ok(if refused.is_empty() { 0 } else { 2 })
}
fn brush_engine_description(worker_available: bool) -> &'static str {
if worker_available {
" · brush — carried bash-in-Rust worker with static command/file policies; descendant confinement requires the native backend"
} else {
" · brush — unavailable: authenticated worker transport is not implemented on this platform; full shell grammar remains unavailable"
}
}
#[cfg(test)]
mod tests {
use super::*;
use newt_core::ocap::{Guarantee, RuntimeEvidence, LINUX_CEILING, MACOS_CEILING};
#[test]
fn brush_description_reports_transport_limit_without_parity_claims() {
let unavailable = brush_engine_description(false);
assert!(unavailable.contains("unavailable"));
assert!(unavailable.contains("authenticated worker transport"));
assert!(!unavailable.contains("Windows full-access default"));
let available = brush_engine_description(true);
assert!(available.contains("static command/file policies"));
assert!(!available.contains("confines restricted exec too"));
}
#[test]
fn posture_lines_are_derived_from_the_report_not_prose() {
let report = SecurityReport::from_parts(&LINUX_CEILING, &RuntimeEvidence::current());
let lines = security_posture_lines(&report);
assert_eq!(lines.len(), Guarantee::ALL.len() + 1);
assert_eq!(lines[0], "platform: linux");
assert!(lines
.iter()
.any(|l| l == "disclosure-filtering: OPEN (disclosure-gate-live-path)"));
{
let mut filter = newt_core::ocap::DisclosureFilter::new();
filter.register("sk-probe-9f3a2b7c1d4e6a8b");
let _guard = newt_core::ocap::scoped_session_disclosure(filter);
let live = SecurityReport::from_parts(&LINUX_CEILING, &RuntimeEvidence::current());
assert!(security_posture_lines(&live)
.iter()
.any(|l| l == "disclosure-filtering: enforced"));
}
assert!(lines
.iter()
.any(|l| l.contains("network-confinement: OPEN (b1-os-isolation)")));
}
#[test]
fn unsupported_platform_never_renders_a_linux_equivalent_claim() {
let all_verified = {
let v = || newt_core::ocap::Verification::Verified {
evidence: "synthetic".into(),
};
RuntimeEvidence {
b1: v(),
disclosure: v(),
fs_object_bound: v(),
constrained_executor: v(),
fail_closed: v(),
}
};
let report = SecurityReport::from_parts(&MACOS_CEILING, &all_verified);
let lines = security_posture_lines(&report);
assert_eq!(lines[0], "platform: macos");
assert!(lines
.iter()
.any(|l| l == "fs-confinement: unsupported on macos"));
assert!(lines
.iter()
.any(|l| l == "process-confinement: unsupported on macos"));
assert!(!lines.iter().any(|l| l == "fs-confinement: enforced"));
}
}