use super::*;
use crate::tabs::{TabAction, TabSet};
use newt_core::lifecycle::new_session_id;
fn cfg_with(names: &[&str]) -> newt_core::ResolvedConfig {
newt_core::ResolvedConfig::unrequested(newt_core::Config {
default_backend: names.first().map(|n| (*n).to_string()),
backends: names
.iter()
.map(|name| newt_core::BackendConfig {
name: (*name).to_string(),
endpoint: "http://backend.test:1".to_string(),
model: Some("m0".to_string()),
kind: Some(newt_core::BackendKind::Openai),
..Default::default()
})
.collect(),
..Default::default()
})
}
struct Harness {
_root: tempfile::TempDir,
_ws: tempfile::TempDir,
store: newt_core::ConversationStore,
persona_store: crate::PersonaStore,
workspace: String,
memory: newt_core::MemoryManager,
system: String,
active_persona: Option<crate::Persona>,
active_conversation_id: String,
compress_state: newt_core::CompressState,
scratchpad: newt_core::SessionScratchpadStore,
step_ledger: newt_core::SessionStepLedger,
active_prompt_context: Option<newt_core::TurnPromptContext>,
mode_states: crate::ConversationModeStates,
baseline: crate::PreferenceBaseline,
pending: newt_core::PreferenceActions,
base_provider: Option<String>,
base_model: Option<String>,
cfg: newt_core::ResolvedConfig,
choice: crate::BackendChoice,
inf_url: String,
inf_model: String,
inf_kind: newt_core::BackendKind,
inf_key: Option<String>,
inf_context_window: Option<u32>,
turns_this_conversation: usize,
last_resume_listing: Vec<String>,
active_roadmap_id: Option<String>,
interrupted_objective: Option<newt_core::TurnPromptContext>,
input_stash: String,
}
impl Harness {
fn new(backends: &[&str]) -> (Self, TabSet) {
let root = tempfile::tempdir().unwrap();
let ws = tempfile::tempdir().unwrap();
let store = newt_core::ConversationStore::new(root.path(), ws.path(), 100).unwrap();
let cfg = cfg_with(backends);
let choice = crate::resolve_backend_choice(&cfg).expect("test configs resolve");
let first = newt_core::new_conversation_id();
store.claim(&first).unwrap();
let workspace = ws.path().to_string_lossy().into_owned();
let memory = newt_core::MemoryManager::new();
let system = String::new();
let tabs = TabSet::new(new_session_id(), &first);
(
Self {
_root: root,
_ws: ws,
store,
persona_store: crate::PersonaStore::new(std::path::PathBuf::from("/nonexistent")),
workspace,
memory,
system,
active_persona: None,
active_conversation_id: first,
compress_state: newt_core::CompressState::new(),
scratchpad: newt_core::SessionScratchpadStore::default(),
step_ledger: newt_core::SessionStepLedger::default(),
active_prompt_context: None,
mode_states: crate::ConversationModeStates::default(),
baseline: crate::PreferenceBaseline::snapshot(None, None),
pending: newt_core::PreferenceActions::default(),
base_provider: None,
base_model: None,
inf_url: choice.url.clone(),
inf_model: choice.active_model.clone().unwrap_or_default(),
inf_kind: choice.kind,
inf_key: choice.api_key.clone(),
inf_context_window: choice.context_window,
choice,
cfg,
turns_this_conversation: 0,
last_resume_listing: Vec::new(),
active_roadmap_id: None,
interrupted_objective: None,
input_stash: String::new(),
},
tabs,
)
}
fn ctx(&mut self) -> TabSwitchCtx<'_> {
TabSwitchCtx {
store: &self.store,
persona_store: &self.persona_store,
workspace: &self.workspace,
memory: &mut self.memory,
system: &mut self.system,
active_persona: &mut self.active_persona,
active_conversation_id: &mut self.active_conversation_id,
compress_state: &mut self.compress_state,
scratchpad: &self.scratchpad,
step_ledger: &self.step_ledger,
active_prompt_context: &mut self.active_prompt_context,
mode_states: &self.mode_states,
baseline: &self.baseline,
pending: &mut self.pending,
base_provider: &mut self.base_provider,
base_model: &mut self.base_model,
cfg: &self.cfg,
choice: &mut self.choice,
inf_url: &mut self.inf_url,
inf_model: &mut self.inf_model,
inf_kind: &mut self.inf_kind,
inf_key: &mut self.inf_key,
inf_context_window: &mut self.inf_context_window,
turns_this_conversation: &mut self.turns_this_conversation,
last_resume_listing: &mut self.last_resume_listing,
active_roadmap_id: &mut self.active_roadmap_id,
interrupted_objective: &mut self.interrupted_objective,
input_stash: &mut self.input_stash,
color: false,
verbose: false,
}
}
fn open_tab_on(&mut self, tabs: &mut TabSet, id: &str) {
self.store.claim(id).unwrap();
let mut ctx = self.ctx();
ctx.deactivate(tabs);
let (_, handoff) = tabs.open(new_session_id(), id);
*ctx.active_conversation_id = id.to_string();
let incoming =
preflight(ctx.store, ctx.persona_store, id, None).expect("fixture row must preflight");
ctx.commit_incoming(tabs, id, incoming);
handoff.apply();
}
fn durable(&self, title: &str) -> String {
let id = self.store.create(title, None).unwrap();
self.store.append_turn(&id, "q", "a").unwrap();
id
}
fn snapshot(&self, tabs: &TabSet) -> Snapshot {
use newt_core::{ScratchpadStore, StepLedger};
let scratchpad: Vec<(String, String)> = self.scratchpad.entries().into_iter().collect();
Snapshot {
conversation_id: self.active_conversation_id.clone(),
inf_url: self.inf_url.clone(),
inf_model: self.inf_model.clone(),
inf_kind: self.inf_kind,
inf_key_fingerprint: fingerprint(self.inf_key.as_deref()),
inf_context_window: self.inf_context_window,
choice_name: self.choice.name.clone(),
provider: std::env::var("NEWT_PROVIDER").ok(),
model: std::env::var("NEWT_DGX_MODEL").ok(),
cognition: newt_core::cognition::cli_cognition(),
tenacity: newt_core::tenacity::cli_tenacity(),
initiative: newt_core::initiative::cli_initiative(),
persona_cognition: newt_core::cognition::persona_cognition(),
persona_initiative: newt_core::initiative::persona_initiative(),
persona: self.active_persona.as_ref().map(|p| p.name.clone()),
scratchpad,
plan_steps: self.step_ledger.steps(),
has_prompt_context: self.active_prompt_context.is_some(),
system: self.system.clone(),
turns: self.turns_this_conversation,
roadmap: self.active_roadmap_id.clone(),
resume_listing: self.last_resume_listing.clone(),
input_stash: self.input_stash.clone(),
degraded: tabs.active().pin_degraded.as_ref().map(|d| d.summary()),
}
}
}
#[derive(Debug, Clone, PartialEq)]
struct Snapshot {
conversation_id: String,
inf_url: String,
inf_model: String,
inf_kind: newt_core::BackendKind,
inf_key_fingerprint: Option<u64>,
inf_context_window: Option<u32>,
choice_name: String,
provider: Option<String>,
model: Option<String>,
cognition: newt_core::cognition::CognitionOverride,
tenacity: Option<newt_core::Tenacity>,
initiative: Option<newt_core::Initiative>,
persona_cognition: Option<newt_core::role_profile::Cognition>,
persona_initiative: Option<newt_core::Initiative>,
persona: Option<String>,
scratchpad: Vec<(String, String)>,
plan_steps: Vec<newt_core::Step>,
has_prompt_context: bool,
system: String,
turns: usize,
roadmap: Option<String>,
resume_listing: Vec<String>,
input_stash: String,
degraded: Option<String>,
}
fn fingerprint(secret: Option<&str>) -> Option<u64> {
use std::hash::{Hash, Hasher};
secret.map(|s| {
let mut h = std::collections::hash_map::DefaultHasher::new();
s.hash(&mut h);
h.finish()
})
}
fn guard() -> newt_core::test_guard::GlobalSettingsGuard {
newt_core::test_guard::GlobalSettingsGuard::acquire()
}
#[test]
fn tab_new_produces_genuinely_isolated_state() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol"]);
{
use newt_core::{ScratchpadStore, StepLedger};
h.scratchpad.set("current_task", "tab A's task".into());
h.step_ledger.restore(&newt_core::PlanSnapshot::default());
assert!(h.scratchpad.get("current_task").is_some());
}
h.turns_this_conversation = 9;
h.active_roadmap_id = Some("road-a".into());
h.interrupted_objective = None;
let outgoing = h.active_conversation_id.clone();
let mut ctx = h.ctx();
let _ = create_fresh_tab(&mut ctx, &mut tabs).expect("a brand-new id always claims");
assert_ne!(h.active_conversation_id, outgoing, "a NEW conversation");
assert_eq!(tabs.len(), 2);
assert_eq!(tabs.active().conversation_id(), h.active_conversation_id);
{
use newt_core::ScratchpadStore;
assert!(
h.scratchpad.get("current_task").is_none(),
"a fresh tab must not inherit the outgoing conversation's scratchpad"
);
}
assert_eq!(h.turns_this_conversation, 0, "fresh turn count");
assert_eq!(h.active_roadmap_id, None, "fresh roadmap binding");
assert!(h.active_prompt_context.is_none(), "fresh prompt receipt");
}
#[test]
fn startup_then_tab_new_then_tab_1_then_tab_2() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol"]);
let tab1_conversation = h.active_conversation_id.clone();
{
let mut ctx = h.ctx();
let _ = create_fresh_tab(&mut ctx, &mut tabs).unwrap();
}
let tab2_conversation = h.active_conversation_id.clone();
assert!(
!h.store.exists(&tab2_conversation).unwrap(),
"fixture precondition: the fresh tab has no materialized row"
);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 0).expect("/tab 1");
}
assert_eq!(h.active_conversation_id, tab1_conversation);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 1)
.expect("/tab 2 must work even though tab 2 has no conversation row yet");
}
assert_eq!(h.active_conversation_id, tab2_conversation);
assert_eq!(tabs.active_index(), 1);
}
fn persona_named(name: &str) -> crate::Persona {
crate::test_persona(name, "be helpful", std::path::PathBuf::from("/dev/null"))
}
#[test]
fn a_rowless_tab_keeps_its_own_persona_across_a_visit_to_another() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol"]);
let dir = h._root.path().join("personas");
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(dir.join("p.md"), "Be helpful as persona P.").unwrap();
std::fs::write(dir.join("q.md"), "Be helpful as persona Q.").unwrap();
h.persona_store = crate::PersonaStore::new(dir);
let p = h.persona_store.load("p").unwrap();
let q = h.persona_store.load("q").unwrap();
let a = h.active_conversation_id.clone();
h.store.create_with_id(&a, "A", None).unwrap();
h.active_persona = Some(p.clone());
{
let mut ctx = h.ctx();
let _ = create_fresh_tab(&mut ctx, &mut tabs).unwrap();
}
let b = h.active_conversation_id.clone();
assert!(!h.store.exists(&b).unwrap(), "B is rowless");
assert_eq!(
h.active_persona,
Some(p.clone()),
"a fresh tab keeps the persona active when it was opened, as /new does"
);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 0).unwrap();
}
assert_eq!(h.active_persona, Some(p.clone()));
h.active_persona = Some(q);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 1).unwrap();
}
assert_eq!(
h.active_persona,
Some(p),
"returning to the rowless tab restores ITS persona, not the other tab's"
);
assert!(
!h.store.exists(&b).unwrap(),
"and visiting a tab still creates no ghost /resume row"
);
}
#[test]
fn a_fresh_tab_reseats_its_personas_initiative() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol"]);
let dir = h._root.path().join("personas");
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(
dir.join("eager.md"),
"+++\ninitiative = \"eager\"\n+++\n\nAct early.",
)
.unwrap();
std::fs::write(dir.join("calm.md"), "Take your time.").unwrap();
h.persona_store = crate::PersonaStore::new(dir);
let eager = h.persona_store.load("eager").unwrap();
let calm = h.persona_store.load("calm").unwrap();
assert_eq!(eager.profile.initiative, Some(newt_core::Initiative::Eager));
let a = h.active_conversation_id.clone();
h.store.create_with_id(&a, "A", None).unwrap();
h.active_persona = Some(eager.clone());
crate::seat_persona_dials(Some(&eager));
{
let mut ctx = h.ctx();
let _ = create_fresh_tab(&mut ctx, &mut tabs).unwrap();
}
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 0).unwrap();
}
h.active_persona = Some(calm.clone());
crate::seat_persona_dials(Some(&calm));
assert_eq!(newt_core::initiative::persona_initiative(), None);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 1).unwrap();
}
assert_eq!(
h.active_persona.as_ref().map(|p| p.name.as_str()),
Some("eager")
);
assert_eq!(
newt_core::initiative::persona_initiative(),
Some(newt_core::Initiative::Eager),
"the fresh tab's persona initiative is back in force"
);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 0).unwrap();
}
assert_eq!(
newt_core::initiative::persona_initiative(),
None,
"and does not leak into a tab without it"
);
}
#[test]
fn renaming_an_inactive_rowless_tab_does_not_capture_the_live_persona() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol"]);
let a = h.active_conversation_id.clone();
h.store.create_with_id(&a, "A", None).unwrap();
h.active_persona = None;
{
let mut ctx = h.ctx();
let _ = create_fresh_tab(&mut ctx, &mut tabs).unwrap();
}
let b = h.active_conversation_id.clone();
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 0).unwrap();
}
h.active_persona = Some(persona_named("Q"));
{
let mut ctx = h.ctx();
handle_tab_action(
TabAction::Rename {
index: Some(1),
title: "B titled".into(),
},
&mut ctx,
&mut tabs,
);
}
assert!(h.store.exists(&b).unwrap(), "rename materialized B's row");
let record = h.store.load(&b).unwrap();
assert_eq!(
record.persona, None,
"B was created with no persona; renaming it from a Q tab must not stamp Q"
);
assert_eq!(record.title, "B titled");
assert!(
tabs.get(1).unwrap().fresh_seed.is_none(),
"the seed is consumed once the row exists"
);
}
#[test]
fn a_preference_change_in_a_rowless_tab_survives_a_switch() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol", "other"]);
let a = h.active_conversation_id.clone();
h.store.create_with_id(&a, "A", None).unwrap();
{
let mut ctx = h.ctx();
let _ = create_fresh_tab(&mut ctx, &mut tabs).unwrap();
}
let b = h.active_conversation_id.clone();
newt_core::runtime::mark_backend_pick("other");
{
let mut ctx = h.ctx();
*ctx.pending = newt_core::runtime::drain_preference_actions();
activate_tab(&mut ctx, &mut tabs, 0).expect("switch away");
}
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 1).expect("switch back");
}
assert!(
!h.pending.is_empty(),
"the rowless tab's unwritten preference action came back with it"
);
assert!(!h.store.exists(&b).unwrap(), "still no ghost row");
h.store.create_with_id(&b, "B", None).unwrap();
crate::persist_preference_actions(
Some(&h.store),
&b,
&mut h.pending,
&mut h.base_provider,
&mut h.base_model,
)
.unwrap();
assert_eq!(
h.store.preference_pin(&b).unwrap().and_then(|p| p.backend),
Some("other".to_string()),
"the first durable write materializes the preference chosen while rowless"
);
}
#[test]
fn the_seed_is_dropped_once_the_row_materializes_and_cannot_re_apply() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol"]);
let dir = h._root.path().join("personas");
std::fs::create_dir_all(&dir).unwrap();
h.persona_store = crate::PersonaStore::new(&dir);
for (name, prompt) in [
("seeded", "The persona captured before the first turn."),
(
"durable",
"The persona selected before the row materializes.",
),
] {
std::fs::write(dir.join(format!("{name}.md")), prompt).unwrap();
}
let a = h.active_conversation_id.clone();
h.store.create_with_id(&a, "A", None).unwrap();
h.active_persona = Some(h.persona_store.load("seeded").unwrap());
{
let mut ctx = h.ctx();
let _ = create_fresh_tab(&mut ctx, &mut tabs).unwrap();
}
let b = h.active_conversation_id.clone();
assert!(tabs.active().fresh_seed.is_some(), "rowless: seed present");
let durable = h.persona_store.load("durable").unwrap();
h.active_persona = Some(durable.clone());
h.store.create_with_id(&b, "B", Some("durable")).unwrap();
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 0).unwrap();
}
assert!(
tabs.get(1).unwrap().fresh_seed.is_none(),
"the seed is retired the moment the row exists"
);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 1).unwrap();
}
assert_eq!(
h.active_persona,
Some(durable),
"restore must use the current selected persona, not resurrect the stale seed"
);
assert!(tabs.active().fresh_seed.is_none());
}
#[test]
fn tab_new_alone_creates_no_ghost_resume_row() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol"]);
let before = h.store.list().map(|v| v.len()).unwrap_or(0);
{
let mut ctx = h.ctx();
let _ = create_fresh_tab(&mut ctx, &mut tabs).unwrap();
let _ = create_fresh_tab(&mut ctx, &mut tabs).unwrap();
}
assert_eq!(
h.store.list().map(|v| v.len()).unwrap_or(0),
before,
"two fresh tabs, zero new rows in the resume listing"
);
}
#[test]
fn a_to_b_to_a_restores_a_exactly() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol", "other"]);
let a_conversation = h.active_conversation_id.clone();
h.store.create_with_id(&a_conversation, "A", None).unwrap();
let b_conversation = h.durable("B");
h.open_tab_on(&mut tabs, &b_conversation);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 0).expect("settle on A");
}
h.turns_this_conversation = 4;
h.active_roadmap_id = Some("road-a".into());
h.input_stash = "half typed in A".into();
let a_before = h.snapshot(&tabs);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 1).expect("A→B");
}
h.turns_this_conversation = 77;
h.active_roadmap_id = Some("road-b".into());
h.input_stash = "half typed in B".into();
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 0).expect("B→A");
}
assert_eq!(
h.snapshot(&tabs),
a_before,
"A must come back exactly as it was left"
);
assert_eq!(h.active_conversation_id, a_conversation);
}
#[test]
fn an_unpinned_tab_does_not_inherit_another_tabs_pin() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol", "other"]);
let unpinned = h.active_conversation_id.clone();
let pinned = h.durable("pinned");
h.store.claim(&pinned).unwrap();
h.store
.update_preference_pin(
&pinned,
&newt_core::OperatorPreferencePin {
backend: Some("other".into()),
tenacity: Some(newt_core::Tenacity::Relentless),
..Default::default()
},
)
.unwrap();
h.open_tab_on(&mut tabs, &pinned);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 0).expect("settle on the unpinned tab");
activate_tab(&mut ctx, &mut tabs, 1).expect("activate the pinned tab");
}
assert_eq!(
std::env::var("NEWT_PROVIDER").ok().as_deref(),
Some("other"),
"the pinned tab routes to its pin"
);
assert_eq!(
newt_core::tenacity::cli_tenacity(),
Some(newt_core::Tenacity::Relentless)
);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 0).expect("back to the unpinned tab");
}
assert_eq!(h.active_conversation_id, unpinned);
assert!(
std::env::var("NEWT_PROVIDER").is_err(),
"an unpinned tab resolves to the baseline, never the other tab's backend"
);
assert_eq!(
newt_core::tenacity::cli_tenacity(),
None,
"and never the other tab's dials"
);
}
#[test]
fn a_dial_change_survives_a_switch_away_and_back() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol", "other"]);
let a = h.active_conversation_id.clone();
h.store.create_with_id(&a, "A", None).unwrap();
let b = h.durable("B");
h.open_tab_on(&mut tabs, &b);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 1).unwrap();
activate_tab(&mut ctx, &mut tabs, 0).unwrap();
}
newt_core::runtime::mark_tenacity_choice(Some(newt_core::Tenacity::Relentless));
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 1).expect("switch away");
}
assert_eq!(
newt_core::tenacity::cli_tenacity(),
None,
"B is unpinned, so it shows the baseline — not A's change"
);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 0).expect("switch back");
}
assert_eq!(
newt_core::tenacity::cli_tenacity(),
Some(newt_core::Tenacity::Relentless),
"the dial change was flushed on deactivation and comes back with A"
);
}
#[test]
fn two_tabs_with_different_pins_swap_cleanly_in_both_directions() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol", "other"]);
let a = h.active_conversation_id.clone();
h.store.create_with_id(&a, "A", None).unwrap();
h.store
.update_preference_pin(
&a,
&newt_core::OperatorPreferencePin {
backend: Some("sol".into()),
tenacity: Some(newt_core::Tenacity::Relentless),
..Default::default()
},
)
.unwrap();
let b = h.durable("B");
h.store
.update_preference_pin(
&b,
&newt_core::OperatorPreferencePin {
backend: Some("other".into()),
cognition: Some("meticulous".into()),
..Default::default()
},
)
.unwrap();
h.open_tab_on(&mut tabs, &b);
let posture = |h: &Harness| {
(
std::env::var("NEWT_PROVIDER").ok(),
h.choice.name.clone(),
h.inf_url.clone(),
h.inf_model.clone(),
h.inf_kind,
newt_core::cognition::cli_cognition(),
newt_core::tenacity::cli_tenacity(),
)
};
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 0).unwrap();
}
let on_a = posture(&h);
assert_eq!(on_a.0.as_deref(), Some("sol"));
assert_eq!(on_a.6, Some(newt_core::Tenacity::Relentless));
assert_eq!(
newt_core::cognition::cli_cognition(),
newt_core::cognition::CognitionOverride::Unset,
"A pins no cognition, so it must show the baseline — not B's"
);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 1).unwrap();
}
let on_b = posture(&h);
assert_eq!(on_b.0.as_deref(), Some("other"));
assert_eq!(
on_b.6, None,
"B pins no tenacity, so A's Relentless must NOT survive the switch"
);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 0).unwrap();
}
assert_eq!(
posture(&h),
on_a,
"A→B→A restores A's whole posture exactly"
);
}
#[test]
fn the_outgoing_tabs_claim_is_held_across_a_switch() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol"]);
let a = h.active_conversation_id.clone();
h.store.create_with_id(&a, "A", None).unwrap();
let b = h.durable("B");
h.open_tab_on(&mut tabs, &b);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 0).unwrap();
activate_tab(&mut ctx, &mut tabs, 1).unwrap();
}
let mut held = tabs.claimed_conversations();
held.sort_unstable();
let mut expected = vec![a.as_str(), b.as_str()];
expected.sort_unstable();
assert_eq!(held, expected, "both claims survive the switch");
}
#[test]
fn closing_a_tab_leaves_its_conversation_resumable() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol"]);
let b = h.durable("B");
h.open_tab_on(&mut tabs, &b);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 0).unwrap();
close_tab(&mut ctx, &mut tabs, 1).unwrap();
}
assert!(
h.store.exists(&b).unwrap(),
"close must not end the conversation — /end is that verb"
);
assert!(matches!(
h.store.claim(&b).unwrap(),
newt_core::ClaimOutcome::Claimed
));
assert!(
tabs.find_by_conversation(&b).is_none(),
"and no tab still names it"
);
}
#[test]
fn resume_of_a_conversation_open_elsewhere_activates_that_tab() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol"]);
let a = h.active_conversation_id.clone();
let b = h.durable("B");
h.open_tab_on(&mut tabs, &b);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 0).unwrap();
}
let adopted = {
let mut ctx = h.ctx();
adopt_conversation(&mut ctx, &mut tabs, &b).unwrap()
};
assert!(
matches!(adopted, Adopted::ActivatedExistingTab { index: 1, .. }),
"got {adopted:?}"
);
assert_eq!(h.active_conversation_id, b);
assert_eq!(tabs.len(), 2, "no second tab was opened for the same row");
assert_eq!(h.active_conversation_id, tabs.active().conversation_id());
assert_ne!(a, b);
}
#[test]
fn a_second_front_door_also_activates_rather_than_duplicating() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol"]);
let b = h.durable("B");
h.open_tab_on(&mut tabs, &b);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 0).unwrap();
}
for _ in 0..2 {
let adopted = {
let mut ctx = h.ctx();
adopt_conversation(&mut ctx, &mut tabs, &b).unwrap()
};
assert!(matches!(
adopted,
Adopted::ActivatedExistingTab { .. } | Adopted::AlreadyHere
));
}
assert_eq!(tabs.len(), 2);
}
#[test]
fn two_tabs_can_never_hold_the_same_conversation() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol"]);
let shared = h.durable("shared");
assert!(matches!(
h.store.claim(&shared).unwrap(),
newt_core::ClaimOutcome::Claimed
));
assert!(matches!(
h.store.claim(&shared).unwrap(),
newt_core::ClaimOutcome::Claimed
));
h.open_tab_on(&mut tabs, &shared);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 0).unwrap();
}
let adopted = {
let mut ctx = h.ctx();
adopt_conversation(&mut ctx, &mut tabs, &shared).unwrap()
};
assert!(matches!(adopted, Adopted::ActivatedExistingTab { .. }));
let mut ids: Vec<&str> = tabs.claimed_conversations();
ids.sort_unstable();
let before = ids.len();
ids.dedup();
assert_eq!(
ids.len(),
before,
"every open tab names a DISTINCT conversation"
);
}
#[test]
fn a_failed_activation_leaves_the_current_tab_untouched() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol"]);
let b = h.durable("B");
h.open_tab_on(&mut tabs, &b);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 0).unwrap();
}
h.input_stash = "typed in A".into();
h.turns_this_conversation = 3;
let before = h.snapshot(&tabs);
let active_before = tabs.active_index();
let owner_before = newt_core::lifecycle::active_session();
let err = {
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 9).unwrap_err()
};
assert!(matches!(err, TabError::OutOfRange { .. }));
assert_eq!(
h.snapshot(&tabs),
before,
"a refused switch mutates nothing"
);
assert_eq!(tabs.active_index(), active_before);
assert_eq!(newt_core::lifecycle::active_session(), owner_before);
}
#[test]
fn a_late_validation_failure_leaves_the_session_completely_untouched() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol", "other"]);
let a = h.active_conversation_id.clone();
h.store.create_with_id(&a, "A", None).unwrap();
let b = h.durable("B");
h.open_tab_on(&mut tabs, &b);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 0).expect("settle on A");
}
{
use newt_core::ScratchpadStore;
h.scratchpad.set("current_task", "A's task".into());
}
h.turns_this_conversation = 6;
h.active_roadmap_id = Some("road-a".into());
h.last_resume_listing = vec!["row-1".into()];
h.input_stash = "half typed in A".into();
let before = h.snapshot(&tabs);
let active_before = tabs.active_index();
let owner_before = newt_core::lifecycle::active_session();
let claims_before = {
let mut c = tabs.claimed_conversations();
c.sort_unstable();
c.iter().map(|s| s.to_string()).collect::<Vec<_>>()
};
assert!(h.store.exists(&b).unwrap(), "B's row exists and loads");
crate::restore_prepare_seam::fail_after_load_for(&b);
let err = {
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 1).unwrap_err()
};
crate::restore_prepare_seam::clear();
assert!(
matches!(err, TabError::PreflightFailed { .. }),
"a late validation failure must abort at Stage 0, got {err:?}"
);
assert_eq!(
h.snapshot(&tabs),
before,
"no field of the live session may change on a refused switch"
);
assert_eq!(tabs.active_index(), active_before, "active tab unchanged");
assert_eq!(
newt_core::lifecycle::active_session(),
owner_before,
"lifecycle ownership did not move"
);
let mut claims_after = tabs.claimed_conversations();
claims_after.sort_unstable();
assert_eq!(
claims_after
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>(),
claims_before,
"no claim was taken or released"
);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 0).expect("A is still activatable");
}
}
#[test]
fn a_late_validation_failure_through_adoption_also_changes_nothing() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol"]);
let a = h.active_conversation_id.clone();
h.store.create_with_id(&a, "A", None).unwrap();
let b = h.durable("B");
h.open_tab_on(&mut tabs, &b);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 0).unwrap();
}
let before = h.snapshot(&tabs);
let owner_before = newt_core::lifecycle::active_session();
crate::restore_prepare_seam::fail_after_load_for(&b);
let outcome = {
let mut ctx = h.ctx();
adopt_conversation(&mut ctx, &mut tabs, &b)
};
crate::restore_prepare_seam::clear();
assert!(outcome.is_err(), "adoption must propagate the abort");
assert_eq!(h.snapshot(&tabs), before);
assert_eq!(newt_core::lifecycle::active_session(), owner_before);
assert_eq!(tabs.active_index(), 0);
}
#[test]
fn closing_the_active_tab_activates_the_neighbor_before_releasing() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol"]);
let a = h.active_conversation_id.clone();
let b = h.durable("B");
h.open_tab_on(&mut tabs, &b);
let closed = {
let mut ctx = h.ctx();
close_tab(&mut ctx, &mut tabs, 1).expect("close the active tab")
};
assert_eq!(closed.tab.conversation_id(), b);
assert_eq!(tabs.len(), 1);
assert_eq!(
h.active_conversation_id, a,
"the neighbor is live before anything is released"
);
assert_eq!(
newt_core::lifecycle::active_session().as_deref(),
Some(tabs.active().session_id().as_str()),
"ownership names a tab that is still open"
);
assert!(
h.store.exists(&b).unwrap(),
"closing a tab must not end its conversation"
);
}
#[test]
fn a_failed_neighbor_activation_leaves_the_closing_tab_intact() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol"]);
let a = h.active_conversation_id.clone();
h.store.create_with_id(&a, "A", None).unwrap();
let b = h.durable("B");
h.open_tab_on(&mut tabs, &b);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 1).unwrap();
}
h.input_stash = "typed in B".into();
h.turns_this_conversation = 5;
let before = h.snapshot(&tabs);
let owner_before = newt_core::lifecycle::active_session();
test_seam::fail_preflight_for(&a);
let err = {
let mut ctx = h.ctx();
close_tab(&mut ctx, &mut tabs, 1).unwrap_err()
};
test_seam::clear();
assert!(matches!(err, TabError::PreflightFailed { .. }), "{err:?}");
assert_eq!(tabs.len(), 2, "nothing was removed");
assert_eq!(tabs.active_index(), 1, "B is still the active tab");
assert_eq!(h.snapshot(&tabs), before, "B's live state is untouched");
assert_eq!(
newt_core::lifecycle::active_session(),
owner_before,
"ownership did not transfer on a failed close"
);
assert!(
h.store.exists(&b).unwrap(),
"and B's conversation was never released or ended"
);
}
#[test]
fn closing_one_tab_cannot_release_another_tabs_claim() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol"]);
let a = h.active_conversation_id.clone();
let b = h.durable("B");
h.open_tab_on(&mut tabs, &b);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 0).unwrap();
}
{
let mut ctx = h.ctx();
close_tab(&mut ctx, &mut tabs, 1).unwrap();
}
assert_eq!(tabs.claimed_conversations(), vec![a.as_str()]);
assert!(matches!(
h.store.claim(&a).unwrap(),
newt_core::ClaimOutcome::Claimed
));
}
#[test]
fn an_unestablishable_pin_degrades_the_tab_instead_of_running_at_baseline() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol"]);
let b = h.durable("B");
h.store.claim(&b).unwrap();
h.store
.update_preference_pin(
&b,
&newt_core::OperatorPreferencePin {
backend: Some("a-backend-that-is-gone".into()),
..Default::default()
},
)
.unwrap();
h.open_tab_on(&mut tabs, &b);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 0).unwrap();
}
let switched = {
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 1).expect("activation itself succeeds")
};
let degraded = switched
.degraded
.expect("a pin that cannot be established must degrade the tab");
assert!(
degraded.summary().starts_with("!pin"),
"visible marker: {}",
degraded.summary()
);
assert_eq!(
degraded.pin.backend.as_deref(),
Some("a-backend-that-is-gone"),
"the pin is retained so a retry has something to retry"
);
assert_eq!(
tabs.active().pin_degraded.as_ref().map(|d| d.summary()),
Some(degraded.summary()),
"the tab carries the degraded state, which is what refuses turns"
);
assert!(
std::env::var("NEWT_PROVIDER").is_err(),
"and the session sits at a KNOWN baseline, not a half-applied pin"
);
}
#[test]
fn a_degraded_tab_can_retry_once_the_pin_is_satisfiable() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol"]);
let b = h.durable("B");
h.store.claim(&b).unwrap();
h.store
.update_preference_pin(
&b,
&newt_core::OperatorPreferencePin {
backend: Some("later".into()),
..Default::default()
},
)
.unwrap();
h.open_tab_on(&mut tabs, &b);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 0).unwrap();
activate_tab(&mut ctx, &mut tabs, 1).unwrap();
}
assert!(tabs.active().pin_degraded.is_some());
h.cfg = cfg_with(&["sol", "later"]);
{
let mut ctx = h.ctx();
handle_tab_action(TabAction::Retry, &mut ctx, &mut tabs);
}
assert!(
tabs.active().pin_degraded.is_none(),
"a satisfiable pin clears the degraded state"
);
assert_eq!(
std::env::var("NEWT_PROVIDER").ok().as_deref(),
Some("later"),
"and the pin is now actually in force"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn tab_new_from_a_pinned_tab_reports_the_endpoint_change() {
let _g = guard();
let mut base = cfg_with(&["sol", "other"]).into_config();
base.backends[1].endpoint = "http://elsewhere.test:2".to_string();
let (mut h, mut tabs) = Harness::new(&["sol"]);
h.cfg = newt_core::ResolvedConfig::unrequested(base);
let a = h.active_conversation_id.clone();
h.store.create_with_id(&a, "A", None).unwrap();
h.store
.update_preference_pin(
&a,
&newt_core::OperatorPreferencePin {
backend: Some("other".into()),
..Default::default()
},
)
.unwrap();
let b = h.durable("B");
h.open_tab_on(&mut tabs, &b);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 0).unwrap();
}
assert_eq!(h.inf_url, "http://elsewhere.test:2", "A is pinned away");
let outcome = {
let mut ctx = h.ctx();
create_fresh_tab(&mut ctx, &mut tabs).unwrap()
};
assert_eq!(
h.inf_url, "http://backend.test:1",
"fresh tab is at baseline"
);
assert!(
outcome.url_changed,
"a fresh tab that moves the endpoint must report it so telemetry re-probes"
);
}
#[test]
fn adopting_a_degraded_tab_reports_the_degradation_in_its_outcome() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol"]);
let a = h.active_conversation_id.clone();
h.store.create_with_id(&a, "A", None).unwrap();
let b = h.durable("B");
h.store
.update_preference_pin(
&b,
&newt_core::OperatorPreferencePin {
backend: Some("a-backend-that-is-gone".into()),
..Default::default()
},
)
.unwrap();
h.open_tab_on(&mut tabs, &b);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 0).unwrap();
}
let adopted = {
let mut ctx = h.ctx();
adopt_conversation(&mut ctx, &mut tabs, &b).unwrap()
};
match adopted {
Adopted::ActivatedExistingTab { index, outcome } => {
assert_eq!(index, 1);
let d = outcome
.degraded
.expect("adoption must carry the degradation, not drop it");
assert!(d.summary().starts_with("!pin"), "{}", d.summary());
}
other => panic!("expected an activation, got {other:?}"),
}
assert!(
tabs.active().pin_degraded.is_some(),
"and the tab itself is marked, which is what refuses turns"
);
}
#[test]
fn closing_into_a_degraded_neighbor_reports_the_degradation() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol"]);
let a = h.active_conversation_id.clone();
h.store.create_with_id(&a, "A", None).unwrap();
h.store
.update_preference_pin(
&a,
&newt_core::OperatorPreferencePin {
backend: Some("also-gone".into()),
..Default::default()
},
)
.unwrap();
let b = h.durable("B");
h.open_tab_on(&mut tabs, &b);
let closed = {
let mut ctx = h.ctx();
close_tab(&mut ctx, &mut tabs, 1).unwrap()
};
assert_eq!(closed.tab.conversation_id(), b);
let d = closed
.outcome
.degraded
.expect("close must carry the neighbor's degradation");
assert!(d.summary().starts_with("!pin"), "{}", d.summary());
assert!(tabs.active().pin_degraded.is_some());
}
#[test]
fn exit_flushes_the_active_tab_and_releases_every_claim() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol", "other"]);
let a = h.active_conversation_id.clone();
h.store.create_with_id(&a, "A", None).unwrap();
let b = h.durable("B");
h.open_tab_on(&mut tabs, &b);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 0).unwrap();
}
newt_core::runtime::mark_backend_pick("other");
h.pending = newt_core::runtime::drain_preference_actions();
assert!(!h.pending.is_empty(), "fixture: an unwritten action exists");
let released = {
let mut ctx = h.ctx();
exit_release_all(&mut ctx, &mut tabs)
};
assert_eq!(
h.store.preference_pin(&a).unwrap().and_then(|p| p.backend),
Some("other".to_string()),
"the active tab's last choice is flushed on exit, not dropped"
);
let mut expect = vec![a.clone(), b.clone()];
expect.sort();
let mut got = released;
got.sort();
assert_eq!(
got, expect,
"every open tab's claim is released, not just the active one"
);
}
#[test]
fn the_tab_surface_refusals_are_explicit_and_ordered() {
let lean = tab_surface_refusal(false, false, true).expect("lean refuses");
assert!(lean.contains("rich-TUI"), "{lean}");
assert!(
lean.contains("/resume"),
"names the lean equivalent: {lean}"
);
let eph = tab_surface_refusal(true, true, true).expect("ephemeral refuses");
assert!(eph.contains("ephemeral"), "{eph}");
let none = tab_surface_refusal(true, false, false).expect("a storeless session refuses");
assert!(none.contains("no store"), "{none}");
assert!(tab_surface_refusal(true, false, true).is_none());
assert!(tab_surface_refusal(false, true, true)
.expect("still refuses")
.contains("rich-TUI"));
}
#[test]
fn a_degraded_tab_refuses_the_turn_and_names_the_way_out() {
let clean = degraded_turn_refusal(None);
assert!(clean.is_none(), "a healthy tab does not refuse");
let degraded = crate::PinDegraded {
reasons: vec!["pinned backend `gone` is not configured".into()],
pin: newt_core::OperatorPreferencePin {
backend: Some("gone".into()),
..Default::default()
},
};
let msg = degraded_turn_refusal(Some(°raded)).expect("a degraded tab refuses");
assert!(msg.starts_with("!pin"), "visible marker first: {msg}");
assert!(
msg.contains("was not accepted"),
"the contract is that the PROMPT is not accepted, not merely unsent: {msg}"
);
assert!(
msg.contains("/tab retry"),
"names the recovery command: {msg}"
);
}
#[test]
fn resume_of_an_unopened_conversation_stays_a_resume() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol"]);
let a = h.active_conversation_id.clone();
h.store.create_with_id(&a, "A", None).unwrap();
let elsewhere = h.durable("not open anywhere");
let before = h.snapshot(&tabs);
let adopted = {
let mut ctx = h.ctx();
adopt_conversation(&mut ctx, &mut tabs, &elsewhere).unwrap()
};
assert!(
matches!(adopted, Adopted::ProceedInActiveTab),
"the seam must hand an unopened conversation back to the resume verb, \
not activate it: got {adopted:?}"
);
assert_eq!(
h.snapshot(&tabs),
before,
"and the seam itself mutates nothing — resume's sparse overlay runs at the caller"
);
assert_eq!(tabs.len(), 1, "no tab was opened for it either");
}
#[test]
fn an_interrupted_objective_does_not_leak_into_another_tab() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol"]);
let a = h.active_conversation_id.clone();
h.store.create_with_id(&a, "A", None).unwrap();
let receipt = h
.store
.begin_prompt(
&a,
"A",
None,
newt_core::NewPrompt::operator(b"objective".to_vec(), b"objective".to_vec()),
)
.unwrap();
h.interrupted_objective = Some(receipt);
assert!(h.interrupted_objective.is_some(), "fixture precondition");
let b = h.durable("B");
h.open_tab_on(&mut tabs, &b);
assert!(
h.interrupted_objective.is_none(),
"tab A's interrupted objective must not follow into B — otherwise B's bare \
`continue` would silently resume A's work"
);
{
let mut ctx = h.ctx();
activate_tab(&mut ctx, &mut tabs, 0).expect("back to A");
}
assert!(
h.interrupted_objective.is_some(),
"and it comes back with A, which is why it is stashed rather than dropped"
);
}
#[test]
fn activation_is_history_independent_over_the_full_projected_state() {
let _g = guard();
let build = || {
let (mut h, mut tabs) = Harness::new(&["sol", "other"]);
let a = h.active_conversation_id.clone();
h.store.create_with_id(&a, "A", None).unwrap();
h.store
.update_preference_pin(
&a,
&newt_core::OperatorPreferencePin {
backend: Some("other".into()),
tenacity: Some(newt_core::Tenacity::Relentless),
initiative: Some(newt_core::Initiative::Eager),
..Default::default()
},
)
.unwrap();
let b = h.durable("B");
h.store
.update_preference_pin(
&b,
&newt_core::OperatorPreferencePin {
backend: Some("sol".into()),
cognition: Some("meticulous".into()),
..Default::default()
},
)
.unwrap();
let c = h.durable("C");
h.open_tab_on(&mut tabs, &b);
h.open_tab_on(&mut tabs, &c);
(h, tabs)
};
let (mut from_a, mut tabs_a) = build();
{
let mut ctx = from_a.ctx();
activate_tab(&mut ctx, &mut tabs_a, 0).unwrap();
activate_tab(&mut ctx, &mut tabs_a, 1).unwrap();
}
let via_a = from_a.snapshot(&tabs_a);
let (mut from_c, mut tabs_c) = build();
{
let mut ctx = from_c.ctx();
activate_tab(&mut ctx, &mut tabs_c, 2).unwrap();
activate_tab(&mut ctx, &mut tabs_c, 1).unwrap();
}
let via_c = from_c.snapshot(&tabs_c);
assert_eq!(via_a.provider, via_c.provider);
assert_eq!(via_a.model, via_c.model);
assert_eq!(via_a.cognition, via_c.cognition);
assert_eq!(via_a.tenacity, via_c.tenacity);
assert_eq!(via_a.initiative, via_c.initiative);
assert_eq!(via_a.persona_cognition, via_c.persona_cognition);
assert_eq!(via_a.persona_initiative, via_c.persona_initiative);
assert_eq!(via_a.inf_url, via_c.inf_url);
assert_eq!(via_a.inf_model, via_c.inf_model);
assert_eq!(via_a.inf_kind, via_c.inf_kind);
assert_eq!(via_a.choice_name, via_c.choice_name);
assert_eq!(via_a.inf_context_window, via_c.inf_context_window);
assert_eq!(via_a.persona, via_c.persona);
assert_eq!(via_a.scratchpad, via_c.scratchpad);
assert_eq!(via_a.plan_steps, via_c.plan_steps);
assert_eq!(via_a.turns, via_c.turns);
assert_eq!(via_a.roadmap, via_c.roadmap);
assert_eq!(via_a.input_stash, via_c.input_stash);
assert_eq!(via_a.degraded, via_c.degraded);
assert_eq!(
via_a.cognition,
newt_core::cognition::CognitionOverride::Set(
newt_core::role_profile::Cognition::Meticulous
),
"B's own pin is in force either way"
);
assert_eq!(
via_a.tenacity, None,
"and A's tenacity never survives into B, from either predecessor"
);
assert_eq!(via_a.initiative, None, "nor A's initiative");
}
#[test]
fn authority_state_is_bit_identical_across_any_switch_sequence() {
let _g = guard();
let (mut h, mut tabs) = Harness::new(&["sol", "other"]);
let b = h.durable("B");
h.store.claim(&b).unwrap();
h.store
.update_preference_pin(
&b,
&newt_core::OperatorPreferencePin {
backend: Some("other".into()),
cognition: Some("meticulous".into()),
tenacity: Some(newt_core::Tenacity::Relentless),
initiative: Some(newt_core::Initiative::Eager),
model: Some("m0".into()),
},
)
.unwrap();
h.open_tab_on(&mut tabs, &b);
newt_core::posture::set_active_posture(Some(newt_core::posture::ActivePosture {
name: "locked".into(),
preset_name: "strict".into(),
clamp: newt_core::Caveats::default(),
clamp_summary: "deny writes".into(),
skill_body: Some("Inspect only.".into()),
framing: Some("Keep the session clamp across conversations.".into()),
}));
let authority = || {
(
std::env::var("NEWT_TEAM").ok(),
std::env::var(newt_core::denial_journal::DENIAL_JOURNAL_PATH_ENV).ok(),
std::env::var("NEWT_POSTURE").ok(),
std::env::var("NEWT_EPHEMERAL").ok(),
newt_core::posture::active_posture().map(|p| {
(
p.name,
p.preset_name,
p.clamp,
p.clamp_summary,
p.skill_body,
p.framing,
)
}),
)
};
let before = authority();
for target in [0usize, 1, 0, 1, 0] {
let mut ctx = h.ctx();
let _ = activate_tab(&mut ctx, &mut tabs, target);
assert_eq!(
authority(),
before,
"no tab switch may widen or narrow what the agent is allowed to do"
);
}
let pin = h.store.preference_pin(&b).unwrap().unwrap();
let newt_core::OperatorPreferencePin {
backend: _,
model: _,
cognition: _,
tenacity: _,
initiative: _,
} = pin;
}
fn materialized_persona_fixture() -> (Harness, TabSet) {
let (mut h, tabs) = Harness::new(&["sol"]);
let dir = h._root.path().join("personas");
std::fs::create_dir_all(&dir).unwrap();
h.persona_store = crate::PersonaStore::new(&dir);
for (name, prompt) in [
("birth", "The persona at conversation birth."),
("selected", "The persona selected later in this tab."),
] {
std::fs::write(dir.join(format!("{name}.md")), prompt).unwrap();
}
h.store
.create_with_id(&h.active_conversation_id, "A", Some("birth"))
.unwrap();
h.store
.append_turn(
&h.active_conversation_id,
"Earlier question.",
"Earlier answer.",
)
.unwrap();
h.active_persona = Some(h.persona_store.load("selected").unwrap());
h.system = crate::rebuild_system_prompt(
&h.workspace,
&h.memory,
h.active_persona.as_ref(),
&h.active_conversation_id,
);
(h, tabs)
}
#[test]
fn materialized_tab_restores_its_later_selected_persona() {
let _g = guard();
let (mut h, mut tabs) = materialized_persona_fixture();
let a = h.active_conversation_id.clone();
let selected = h.active_persona.clone().unwrap();
let b = h.durable("B");
h.open_tab_on(&mut tabs, &b);
assert!(h.active_persona.is_none(), "B has no selected persona");
activate_tab(&mut h.ctx(), &mut tabs, 0).unwrap();
assert_eq!(h.active_conversation_id, a);
assert_eq!(h.active_persona, Some(selected.clone()));
assert!(h.system.contains(&selected.prompt));
assert!(!h.system.contains("The persona at conversation birth."));
assert_eq!(
h.store.load(&a).unwrap().persona.as_deref(),
Some("birth"),
"session selection must not rewrite historical birth metadata"
);
}
#[test]
fn a_restored_conversation_reseats_its_personas_initiative() {
let _g = guard();
let (mut h, mut tabs) = materialized_persona_fixture();
std::fs::write(
&h.active_persona.as_ref().unwrap().path,
"+++\ninitiative = \"eager\"\n+++\n\nThe persona selected later in this tab.",
)
.unwrap();
let b = h.durable("B");
h.open_tab_on(&mut tabs, &b);
assert_eq!(
newt_core::initiative::persona_initiative(),
None,
"B has none"
);
activate_tab(&mut h.ctx(), &mut tabs, 0).unwrap();
assert_eq!(
newt_core::initiative::persona_initiative(),
Some(newt_core::Initiative::Eager),
"the restored persona's initiative is in force"
);
activate_tab(&mut h.ctx(), &mut tabs, 1).unwrap();
assert_eq!(newt_core::initiative::persona_initiative(), None);
}
#[test]
fn materialized_tab_reloads_the_selected_personas_current_profile() {
let _g = guard();
let (mut h, mut tabs) = materialized_persona_fixture();
let original = h.active_persona.clone().unwrap();
let b = h.durable("B");
h.open_tab_on(&mut tabs, &b);
std::fs::write(
&original.path,
"+++\ntools = [\"read_file\"]\ncrew = false\n[caveats]\nfs_write = \"none\"\nexec = \"none\"\nnet = \"none\"\n+++\n\nThe newly restricted selected persona.",
)
.unwrap();
let current = h.persona_store.load("selected").unwrap();
assert_ne!(current.profile, original.profile, "the file really changed");
activate_tab(&mut h.ctx(), &mut tabs, 0).unwrap();
assert_eq!(h.active_persona, Some(current.clone()));
assert!(h.system.contains(¤t.prompt));
assert!(!h.system.contains(&original.prompt));
}
#[test]
fn materialized_tab_preserves_synthetic_altitude_without_loading_its_display_name() {
let _g = guard();
let (mut h, mut tabs) = materialized_persona_fixture();
let synthetic = crate::synthetic_altitude_persona(newt_core::Altitude::Coach);
std::fs::write(
h._root.path().join("personas/coach.md"),
"+++\ntools = [\"read_file\"]\ncrew = false\nmodel = \"disk-coach-model\"\n[caveats]\nfs_write = \"none\"\nexec = \"none\"\nnet = \"none\"\n+++\n\nDISK_COACH_PERSONA_MUST_NOT_BE_SELECTED.",
)
.unwrap();
let disk = h.persona_store.load("coach").unwrap();
assert_eq!(
disk.name, synthetic.name,
"the display names really collide"
);
assert_ne!(disk.profile, synthetic.profile);
assert!(disk.profile.caveats.is_some());
assert!(synthetic.path.as_os_str().is_empty());
h.active_persona = Some(synthetic.clone());
h.system = crate::rebuild_system_prompt(
&h.workspace,
&h.memory,
h.active_persona.as_ref(),
&h.active_conversation_id,
);
let b = h.durable("B");
h.open_tab_on(&mut tabs, &b);
activate_tab(&mut h.ctx(), &mut tabs, 0).unwrap();
assert_eq!(h.active_persona, Some(synthetic));
assert!(!h.system.contains(&disk.prompt));
assert!(!h.system.contains("The persona at conversation birth."));
}
#[test]
fn missing_selected_persona_refuses_a_materialized_tab_switch_atomically() {
let _g = guard();
let (mut h, mut tabs) = materialized_persona_fixture();
let selected = h.active_persona.clone().unwrap();
let b = h.durable("B");
h.open_tab_on(&mut tabs, &b);
h.turns_this_conversation = 7;
h.input_stash = "Unsubmitted text in B.".into();
let before = h.snapshot(&tabs);
let active_index = tabs.active_index();
let active_tab_id = tabs.active().session_id().clone();
let owner_before = newt_core::lifecycle::active_session();
std::fs::remove_file(&selected.path).unwrap();
assert!(h.persona_store.load("selected").is_err());
let result = activate_tab(&mut h.ctx(), &mut tabs, 0);
assert!(matches!(result, Err(TabError::PreflightFailed { .. })));
assert_eq!(tabs.active_index(), active_index);
assert_eq!(tabs.active().session_id(), &active_tab_id);
assert_eq!(newt_core::lifecycle::active_session(), owner_before);
assert_eq!(h.snapshot(&tabs), before);
}
#[test]
fn materialized_tab_keeps_a_known_absent_persona_instead_of_resurrecting_birth() {
let _g = guard();
let (mut h, mut tabs) = materialized_persona_fixture();
let a = h.active_conversation_id.clone();
h.active_persona = None;
h.system = crate::rebuild_system_prompt(&h.workspace, &h.memory, None, &a);
let b = h.durable("B");
h.open_tab_on(&mut tabs, &b);
activate_tab(&mut h.ctx(), &mut tabs, 0).unwrap();
assert_eq!(h.active_conversation_id, a);
assert!(h.active_persona.is_none());
assert!(!h.system.contains("The persona at conversation birth."));
assert!(!h.system.contains("The persona selected later in this tab."));
assert_eq!(h.store.load(&a).unwrap().persona.as_deref(), Some("birth"));
}