refactor: move scope authority to pod store

This commit is contained in:
2026-05-30 07:36:17 +09:00
parent 211738132c
commit e10b4ad4f0
15 changed files with 200 additions and 338 deletions
+1 -78
View File
@@ -17,7 +17,7 @@ use std::time::Duration;
use client::{SpawnConfig, spawn_pod};
use crossterm::event::{self, Event as TermEvent, KeyCode, KeyEventKind, KeyModifiers};
use manifest::{ProfileDiscovery, ScopeConfig};
use manifest::ProfileDiscovery;
use ratatui::Terminal;
use ratatui::backend::CrosstermBackend;
use ratatui::layout::{Constraint, Layout};
@@ -42,8 +42,6 @@ pub enum SpawnOutcome {
#[derive(Debug)]
pub enum SpawnError {
Io(io::Error),
Store(session_store::StoreError),
MissingResumeScope { segment_id: SegmentId },
Spawn(client::SpawnError),
}
@@ -51,11 +49,6 @@ impl std::fmt::Display for SpawnError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Io(e) => write!(f, "io error: {e}"),
Self::Store(e) => write!(f, "failed to read session log: {e}"),
Self::MissingResumeScope { segment_id } => write!(
f,
"session {segment_id} has no persisted scope snapshot; refusing resume without explicit scope"
),
Self::Spawn(e) => write!(f, "{e}"),
}
}
@@ -69,12 +62,6 @@ impl From<io::Error> for SpawnError {
}
}
impl From<session_store::StoreError> for SpawnError {
fn from(e: session_store::StoreError) -> Self {
Self::Store(e)
}
}
impl From<client::SpawnError> for SpawnError {
fn from(e: client::SpawnError) -> Self {
Self::Spawn(e)
@@ -111,7 +98,6 @@ pub async fn run(
editing: true,
resume_from,
resume_by_pod_name: false,
resume_scope: None,
profile_choices,
profile_index,
};
@@ -149,10 +135,6 @@ pub async fn run(
}
}
if let Some(id) = form.resume_from {
form.resume_scope = Some(load_resume_scope(id).await?);
}
// Phase 2: launch pod and wait for ready line. Drop the cursor
// out of the name field — subsequent frames are passive status
// updates, not input — so the cursor doesn't end up parked there
@@ -305,7 +287,6 @@ fn form_for_pod_name(pod_name: String, defaults: SpawnDefaults) -> Form {
editing: false,
resume_from: None,
resume_by_pod_name: true,
resume_scope: None,
profile_choices: Vec::new(),
profile_index: 0,
}
@@ -383,7 +364,6 @@ async fn wait_for_ready(
let config = SpawnConfig {
pod_name: form.name.clone(),
profile: form.selected_profile_selector(),
resume_scope: form.resume_scope.clone(),
cwd: form.cwd.clone(),
resume_from: form.resume_from,
resume_by_pod_name: form.resume_by_pod_name,
@@ -399,24 +379,6 @@ async fn wait_for_ready(
})
}
async fn load_resume_scope(segment_id: SegmentId) -> Result<ScopeConfig, SpawnError> {
let store_dir = manifest::paths::sessions_dir().ok_or_else(|| {
io::Error::new(
io::ErrorKind::NotFound,
"could not resolve sessions directory (set INSOMNIA_HOME, INSOMNIA_DATA_DIR, or HOME)",
)
})?;
let store = session_store::FsStore::new(&store_dir)?;
let state = session_store::restore_by_segment(&store, segment_id)?;
let snapshot = state
.pod_scope
.ok_or(SpawnError::MissingResumeScope { segment_id })?;
Ok(ScopeConfig {
allow: snapshot.allow,
deny: snapshot.deny,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MessageKind {
Info,
@@ -453,10 +415,6 @@ struct Form {
/// When true, launch the child with `--pod <name>` so the pod process
/// resolves name-keyed state before falling back to fresh creation.
resume_by_pod_name: bool,
/// Scope snapshot recovered from the source session log. Set only for
/// resume runs and passed through a typed internal restore flag so resume
/// does not silently broaden access.
resume_scope: Option<ScopeConfig>,
/// Optional Nix profile choices passed to `insomnia-pod --profile` for
/// fresh spawns. This is not used for resume/attach flows because those must
/// restore Pod state rather than re-evaluate a profile source.
@@ -616,17 +574,6 @@ fn context_line(form: &Form) -> Line<'_> {
]);
}
if form.resume_scope.is_some() {
return Line::from(vec![
Span::raw(" "),
Span::styled("scope: ", Style::default().fg(Color::DarkGray)),
Span::styled(
"from restored session snapshot",
Style::default().fg(Color::Green),
),
]);
}
match form.scope_origin {
ScopeOrigin::FromProfile => Line::from(vec![
Span::raw(" "),
@@ -670,7 +617,6 @@ mod tests {
editing: true,
resume_from: None,
resume_by_pod_name: false,
resume_scope: None,
profile_choices: Vec::new(),
profile_index: 0,
}
@@ -691,7 +637,6 @@ mod tests {
assert_eq!(f.name_cursor, "agent".chars().count());
assert_eq!(f.resume_from, None);
assert!(f.resume_by_pod_name);
assert!(f.resume_scope.is_none());
assert!(!f.editing);
assert_eq!(
f.message,
@@ -699,28 +644,6 @@ mod tests {
);
}
#[test]
fn resume_scope_snapshot_stays_on_form_for_typed_restore_flag() {
let mut f = form("agent-r");
f.resume_from = Some(session_store::new_segment_id());
f.resume_scope = Some(ScopeConfig {
allow: vec![manifest::ScopeRule {
target: PathBuf::from("/work/example"),
permission: manifest::Permission::Write,
recursive: true,
}],
deny: vec![manifest::ScopeRule {
target: PathBuf::from("/work/example/child"),
permission: manifest::Permission::Write,
recursive: true,
}],
});
let scope = f.resume_scope.as_ref().unwrap();
assert_eq!(scope.allow[0].target, PathBuf::from("/work/example"));
assert_eq!(scope.deny[0].target, PathBuf::from("/work/example/child"));
}
#[test]
fn profile_choices_use_project_registry_default() {
let temp = tempfile::tempdir().unwrap();