feat: display DEFCON posture in banner + prompt
Reads BASCULE_DEFCON_LEVEL from env. At DEFCON <5: Banner: DEFCON level + label (RESTRICTED/CRITICAL/LOCKDOWN) + reason Prompt: [restricted] at DEFCON 3, [DEFCON] at ≤2 DEFCON 5 (peacetime): no DEFCON line in banner, normal prompt. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3c4042ce8e
commit
02bcd58c99
2 changed files with 46 additions and 6 deletions
|
|
@ -162,6 +162,25 @@ fn print_banner(session: &SessionState) {
|
||||||
_ => session.risk_level.clone(),
|
_ => session.risk_level.clone(),
|
||||||
},
|
},
|
||||||
"║".bright_blue());
|
"║".bright_blue());
|
||||||
|
|
||||||
|
// DEFCON line — only shown when not peacetime
|
||||||
|
if session.defcon_level < 5 {
|
||||||
|
let defcon_label = match session.defcon_level {
|
||||||
|
1 => "LOCKDOWN".red().to_string(),
|
||||||
|
2 => "CRITICAL".red().to_string(),
|
||||||
|
3 => "RESTRICTED".yellow().to_string(),
|
||||||
|
4 => "ELEVATED".yellow().to_string(),
|
||||||
|
_ => "PEACETIME".green().to_string(),
|
||||||
|
};
|
||||||
|
println!("{} DEFCON: {:<44}{}", "║".bright_blue(),
|
||||||
|
format!("{} — {}", session.defcon_level, defcon_label),
|
||||||
|
"║".bright_blue());
|
||||||
|
if let Some(ref reason) = session.defcon_reason {
|
||||||
|
let truncated = if reason.len() > 42 { format!("{}...", &reason[..39]) } else { reason.clone() };
|
||||||
|
println!("{} Reason: {:<44}{}", "║".bright_blue(), truncated, "║".bright_blue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
println!("{}", "╚══════════════════════════════════════════════════════════╝".bright_blue());
|
println!("{}", "╚══════════════════════════════════════════════════════════╝".bright_blue());
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
|
|
@ -193,11 +212,18 @@ fn print_summary(session: &SessionState) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_prompt(session: &SessionState) -> DefaultPrompt {
|
fn build_prompt(session: &SessionState) -> DefaultPrompt {
|
||||||
let risk_indicator = match session.risk_level.as_str() {
|
// DEFCON overrides the risk indicator when elevated
|
||||||
|
let risk_indicator = if session.defcon_level <= 2 {
|
||||||
|
"[DEFCON]"
|
||||||
|
} else if session.defcon_level == 3 {
|
||||||
|
"[restricted]"
|
||||||
|
} else {
|
||||||
|
match session.risk_level.as_str() {
|
||||||
"baseline" | "standard" | "ungoverned" => "[governed]",
|
"baseline" | "standard" | "ungoverned" => "[governed]",
|
||||||
"elevated" => "[elevated]",
|
"elevated" => "[elevated]",
|
||||||
"high" | "critical" => "[HIGH]",
|
"high" | "critical" => "[HIGH]",
|
||||||
_ => "[governed]",
|
_ => "[governed]",
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let short_name = session.display_name.split('@').next().unwrap_or(&session.display_name);
|
let short_name = session.display_name.split('@').next().unwrap_or(&session.display_name);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ pub struct SessionState {
|
||||||
pub principal: String,
|
pub principal: String,
|
||||||
pub display_name: String,
|
pub display_name: String,
|
||||||
pub risk_level: String,
|
pub risk_level: String,
|
||||||
|
pub defcon_level: i32,
|
||||||
|
pub defcon_reason: Option<String>,
|
||||||
pub started_at: chrono::DateTime<chrono::Utc>,
|
pub started_at: chrono::DateTime<chrono::Utc>,
|
||||||
pub expires_at: Option<chrono::DateTime<chrono::Utc>>,
|
pub expires_at: Option<chrono::DateTime<chrono::Utc>>,
|
||||||
pub governed_count: u32,
|
pub governed_count: u32,
|
||||||
|
|
@ -40,12 +42,18 @@ impl SessionState {
|
||||||
let display_name = std::env::var("BASCULE_DISPLAY_NAME")
|
let display_name = std::env::var("BASCULE_DISPLAY_NAME")
|
||||||
.unwrap_or_else(|_| display_name_from_did(&principal));
|
.unwrap_or_else(|_| display_name_from_did(&principal));
|
||||||
|
|
||||||
|
let defcon_level = std::env::var("BASCULE_DEFCON_LEVEL")
|
||||||
|
.ok().and_then(|v| v.parse().ok()).unwrap_or(5);
|
||||||
|
let defcon_reason = std::env::var("BASCULE_DEFCON_REASON").ok();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
ac_id: ac.context_id.clone(),
|
ac_id: ac.context_id.clone(),
|
||||||
corpus_cid: corpus_cid.to_string(),
|
corpus_cid: corpus_cid.to_string(),
|
||||||
principal,
|
principal,
|
||||||
display_name,
|
display_name,
|
||||||
risk_level: "standard".to_string(), // TODO: read from AC when broker embeds it
|
risk_level: "standard".to_string(),
|
||||||
|
defcon_level,
|
||||||
|
defcon_reason,
|
||||||
started_at: chrono::Utc::now(),
|
started_at: chrono::Utc::now(),
|
||||||
expires_at,
|
expires_at,
|
||||||
governed_count: 0,
|
governed_count: 0,
|
||||||
|
|
@ -60,12 +68,18 @@ impl SessionState {
|
||||||
let principal = whoami();
|
let principal = whoami();
|
||||||
let display_name = std::env::var("BASCULE_DISPLAY_NAME")
|
let display_name = std::env::var("BASCULE_DISPLAY_NAME")
|
||||||
.unwrap_or_else(|_| principal.clone());
|
.unwrap_or_else(|_| principal.clone());
|
||||||
|
let defcon_level = std::env::var("BASCULE_DEFCON_LEVEL")
|
||||||
|
.ok().and_then(|v| v.parse().ok()).unwrap_or(5);
|
||||||
|
let defcon_reason = std::env::var("BASCULE_DEFCON_REASON").ok();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
ac_id: "ungoverned".to_string(),
|
ac_id: "ungoverned".to_string(),
|
||||||
corpus_cid: corpus_cid.to_string(),
|
corpus_cid: corpus_cid.to_string(),
|
||||||
principal,
|
principal,
|
||||||
display_name,
|
display_name,
|
||||||
risk_level: "ungoverned".to_string(),
|
risk_level: "ungoverned".to_string(),
|
||||||
|
defcon_level,
|
||||||
|
defcon_reason,
|
||||||
started_at: chrono::Utc::now(),
|
started_at: chrono::Utc::now(),
|
||||||
expires_at: None,
|
expires_at: None,
|
||||||
governed_count: 0,
|
governed_count: 0,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue