fix subject authority and bounded memory browsing
This commit is contained in:
@@ -825,7 +825,7 @@ pub(crate) fn is_builtin_subjektiv_consolidation_profile(
|
||||
}
|
||||
|
||||
pub(crate) fn ordinary_subjektiv_features_enabled(manifest: &manifest::WorkerManifest) -> bool {
|
||||
manifest.feature.subjektiv.profile.enabled
|
||||
manifest.feature.subjektiv.execution_enabled()
|
||||
&& !is_builtin_subjektiv_consolidation_profile(manifest)
|
||||
}
|
||||
|
||||
@@ -890,10 +890,10 @@ impl SubjektivConsolidationFeatureInstallPlan {
|
||||
config
|
||||
.validate_execution()
|
||||
.map_err(|message| std::io::Error::new(std::io::ErrorKind::InvalidInput, message))?;
|
||||
if !config.profile.enabled || config.profile.extraction.enabled {
|
||||
if !config.execution_enabled() || config.profile.extraction.enabled {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
"subjektiv consolidation profile requires subject settings without automatic extraction",
|
||||
"subjektiv consolidation profile requires trusted subject settings without automatic extraction",
|
||||
));
|
||||
}
|
||||
let workspace_id = client.workspace_id().ok_or_else(|| {
|
||||
|
||||
@@ -199,7 +199,7 @@ impl SubjektivLifecycleFeature {
|
||||
workspace_context: WorkerWorkspaceContext,
|
||||
event_tx: Option<broadcast::Sender<Event>>,
|
||||
) -> std::io::Result<Option<Self>> {
|
||||
if !lifecycle_enabled || !config.profile.enabled || !config.profile.extraction.enabled {
|
||||
if !lifecycle_enabled || !config.execution_enabled() || !config.profile.extraction.enabled {
|
||||
return Ok(None);
|
||||
}
|
||||
config
|
||||
|
||||
@@ -109,6 +109,12 @@ impl SubjektivSurfaceLifecycleFeature {
|
||||
.subjektiv
|
||||
.validate_execution()
|
||||
.map_err(|message| std::io::Error::new(std::io::ErrorKind::InvalidInput, message))?;
|
||||
if !manifest.feature.subjektiv.execution_enabled() {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
"subjektiv surface generation requires trusted subject attachment",
|
||||
));
|
||||
}
|
||||
if !workspace_client.is_available() || workspace_client.workspace_id().is_none() {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
|
||||
@@ -78,7 +78,7 @@ impl SubjektivMemoryFeature {
|
||||
capture: CommittedSessionCaptureHandle,
|
||||
client: Arc<dyn WorkspaceClient>,
|
||||
) -> std::io::Result<Option<Self>> {
|
||||
if !config.profile.enabled {
|
||||
if !config.execution_enabled() {
|
||||
return Ok(None);
|
||||
}
|
||||
config
|
||||
|
||||
@@ -55,7 +55,7 @@ impl SubjektivSessionFeature {
|
||||
capture: CommittedSessionCaptureHandle,
|
||||
client: Arc<dyn WorkspaceClient>,
|
||||
) -> std::io::Result<Option<Self>> {
|
||||
if !config.profile.enabled {
|
||||
if !config.execution_enabled() {
|
||||
return Ok(None);
|
||||
}
|
||||
config
|
||||
|
||||
@@ -925,25 +925,30 @@ fn bind_child_memory_settings(
|
||||
parent: &manifest::WorkerManifest,
|
||||
child: &mut manifest::WorkerManifest,
|
||||
) -> Result<(), String> {
|
||||
if !child.feature.memory.profile.enabled {
|
||||
return child
|
||||
if child.feature.memory.profile.enabled {
|
||||
let workspace_settings = parent.feature.memory.workspace_settings().ok_or_else(|| {
|
||||
"enabled child Memory feature requires the parent's trusted Workspace settings snapshot"
|
||||
.to_string()
|
||||
})?;
|
||||
child
|
||||
.feature
|
||||
.memory
|
||||
.validate_execution()
|
||||
.map_err(str::to_string);
|
||||
.bind_workspace_settings(workspace_settings)
|
||||
.map_err(str::to_string)?;
|
||||
}
|
||||
let workspace_settings = parent.feature.memory.workspace_settings().ok_or_else(|| {
|
||||
"enabled child Memory feature requires the parent's trusted Workspace settings snapshot"
|
||||
.to_string()
|
||||
})?;
|
||||
child
|
||||
.feature
|
||||
.memory
|
||||
.bind_workspace_settings(workspace_settings)
|
||||
.validate_execution()
|
||||
.map_err(str::to_string)?;
|
||||
|
||||
// An Internal SubWorker has no Backend keyed-singleton lease of its own.
|
||||
// Preserve reusable subjektiv profile policy but never copy the parent's
|
||||
// trusted activation snapshot into the child.
|
||||
child.feature.subjektiv.workspace_settings = None;
|
||||
child
|
||||
.feature
|
||||
.memory
|
||||
.subjektiv
|
||||
.validate_execution()
|
||||
.map_err(str::to_string)
|
||||
}
|
||||
@@ -1292,7 +1297,7 @@ enabled = false
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reviewer_profile_write_scope_exposes_command_tools_and_notifies_parent_controller() {
|
||||
async fn builtin_reviewer_spawn_from_subject_parent_is_inert_and_notifies_parent() {
|
||||
let runtime = TempDir::new().unwrap();
|
||||
let workspace_root = runtime.path().join("project");
|
||||
let bash_output_dir = runtime.path().join("bash-output");
|
||||
@@ -1310,6 +1315,17 @@ enabled = false
|
||||
allow: vec![abs_rule(&workspace_root, Permission::Write)],
|
||||
deny: Vec::new(),
|
||||
};
|
||||
manifest.feature.subjektiv.profile.enabled = true;
|
||||
manifest.feature.subjektiv.profile.extraction.enabled = true;
|
||||
manifest
|
||||
.feature
|
||||
.subjektiv
|
||||
.bind_workspace_settings(manifest::WorkspaceMemorySettingsSnapshot {
|
||||
workspace_id: "workspace-test".to_string(),
|
||||
settings_revision: 1,
|
||||
language: "English".to_string(),
|
||||
})
|
||||
.unwrap();
|
||||
let spawner_scope = SharedScope::new(Scope::from_config(&manifest.scope).unwrap());
|
||||
let registry = SpawnedWorkerRegistry::new_internal("parent".into(), spawner_scope.clone());
|
||||
let workspace_context = crate::worker::WorkerWorkspaceContext::with_client(
|
||||
@@ -1359,7 +1375,7 @@ enabled = false
|
||||
}));
|
||||
let input = serde_json::json!({
|
||||
"name": "reviewer-child",
|
||||
"profile": "project:reviewer",
|
||||
"profile": "builtin:reviewer",
|
||||
"instruction": "role.reviewer",
|
||||
"task": "review immutable commit",
|
||||
"scope": [{
|
||||
@@ -1409,6 +1425,14 @@ enabled = false
|
||||
record.installed_tools
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
record
|
||||
.installed_tools
|
||||
.iter()
|
||||
.all(|name| !name.starts_with("Subjektiv")),
|
||||
"Reviewer child must not install independently scoped subjektiv tools: {:?}",
|
||||
record.installed_tools
|
||||
);
|
||||
assert!(
|
||||
!record
|
||||
.installed_tools
|
||||
@@ -1881,14 +1905,22 @@ enabled = false
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let mut parent = parent_manifest(temp.path(), None);
|
||||
parent.feature.memory.profile.enabled = true;
|
||||
let settings = manifest::WorkspaceMemorySettingsSnapshot {
|
||||
workspace_id: "workspace-1".to_string(),
|
||||
settings_revision: 4,
|
||||
language: "日本語".to_string(),
|
||||
};
|
||||
parent
|
||||
.feature
|
||||
.memory
|
||||
.bind_workspace_settings(manifest::WorkspaceMemorySettingsSnapshot {
|
||||
workspace_id: "workspace-1".to_string(),
|
||||
settings_revision: 4,
|
||||
language: "日本語".to_string(),
|
||||
})
|
||||
.bind_workspace_settings(settings.clone())
|
||||
.unwrap();
|
||||
parent.feature.subjektiv.profile.enabled = true;
|
||||
parent.feature.subjektiv.profile.extraction.enabled = true;
|
||||
parent
|
||||
.feature
|
||||
.subjektiv
|
||||
.bind_workspace_settings(settings)
|
||||
.unwrap();
|
||||
let mut child = parent.clone();
|
||||
child.feature.memory.workspace_settings = None;
|
||||
@@ -1898,6 +1930,15 @@ enabled = false
|
||||
child.feature.memory.workspace_settings(),
|
||||
parent.feature.memory.workspace_settings()
|
||||
);
|
||||
assert!(child.feature.subjektiv.profile.enabled);
|
||||
assert!(!child.feature.subjektiv.execution_enabled());
|
||||
|
||||
let mut ordinary_parent = parent.clone();
|
||||
ordinary_parent.feature.subjektiv.workspace_settings = None;
|
||||
let mut ordinary_child = child.clone();
|
||||
ordinary_child.feature.memory.workspace_settings = None;
|
||||
bind_child_memory_settings(&ordinary_parent, &mut ordinary_child).unwrap();
|
||||
assert!(!ordinary_child.feature.subjektiv.execution_enabled());
|
||||
|
||||
child.feature.memory.profile.enabled = false;
|
||||
assert!(bind_child_memory_settings(&parent, &mut child).is_err());
|
||||
|
||||
@@ -1037,34 +1037,15 @@ permission = "write"
|
||||
#[tokio::test]
|
||||
async fn builtin_orchestrator_exposes_worker_remove_and_workdir_delete() {
|
||||
let workspace = tempfile::tempdir().unwrap();
|
||||
let mut resolved = ProfileResolver::new()
|
||||
let resolved = ProfileResolver::new()
|
||||
.with_workspace_base(workspace.path())
|
||||
.resolve(
|
||||
&ProfileSelector::source_named(ProfileRegistrySource::Builtin, "orchestrator"),
|
||||
ProfileResolveOptions::with_worker_name("orchestrator-worker"),
|
||||
)
|
||||
.unwrap();
|
||||
let settings = manifest::WorkspaceMemorySettingsSnapshot {
|
||||
workspace_id: "workspace-test".to_string(),
|
||||
settings_revision: 1,
|
||||
language: "English".to_string(),
|
||||
};
|
||||
if resolved.manifest.feature.memory.enabled() {
|
||||
resolved
|
||||
.manifest
|
||||
.feature
|
||||
.memory
|
||||
.bind_workspace_settings(settings.clone())
|
||||
.unwrap();
|
||||
}
|
||||
if resolved.manifest.feature.subjektiv.enabled() {
|
||||
resolved
|
||||
.manifest
|
||||
.feature
|
||||
.subjektiv
|
||||
.bind_workspace_settings(settings)
|
||||
.unwrap();
|
||||
}
|
||||
assert!(resolved.manifest.feature.subjektiv.enabled());
|
||||
assert!(!resolved.manifest.feature.subjektiv.execution_enabled());
|
||||
let workspace_context =
|
||||
WorkerWorkspaceContext::with_client(None, Arc::new(NoopWorkspaceClient));
|
||||
let client = MockClient::new(simple_text_events());
|
||||
@@ -1090,6 +1071,47 @@ async fn builtin_orchestrator_exposes_worker_remove_and_workdir_delete() {
|
||||
|
||||
assert!(installed.iter().any(|name| name == "WorkerRemove"));
|
||||
assert!(installed.iter().any(|name| name == "WorkdirDelete"));
|
||||
assert!(
|
||||
installed.iter().all(|name| !name.starts_with("Subjektiv")),
|
||||
"ordinary Orchestrator must not install subject-scoped tools: {installed:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn builtin_coder_commits_without_unattached_subjektiv_lifecycle() {
|
||||
let workspace = tempfile::tempdir().unwrap();
|
||||
let resolved = ProfileResolver::new()
|
||||
.with_workspace_base(workspace.path())
|
||||
.resolve(
|
||||
&ProfileSelector::source_named(ProfileRegistrySource::Builtin, "coder"),
|
||||
ProfileResolveOptions::with_worker_name("coder-worker"),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(resolved.manifest.feature.subjektiv.enabled());
|
||||
assert!(!resolved.manifest.feature.subjektiv.execution_enabled());
|
||||
let client = MockClient::new(simple_text_events());
|
||||
let client_for_assert = client.clone();
|
||||
let (worker, _pwd) = make_worker_with_manifest_and_workspace_context(
|
||||
client,
|
||||
resolved.manifest,
|
||||
WorkerWorkspaceContext::with_client(None, Arc::new(NoopWorkspaceClient)),
|
||||
)
|
||||
.await;
|
||||
let handle = spawn_controller(worker).await;
|
||||
|
||||
handle
|
||||
.send(Method::submit_text(
|
||||
protocol::new_submission_request_id(),
|
||||
"Hello",
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
wait_for_status(&handle, WorkerStatus::Idle).await;
|
||||
let installed = request_tool_names(&wait_for_captured_request(&client_for_assert).await);
|
||||
assert!(
|
||||
installed.iter().all(|name| !name.starts_with("Subjektiv")),
|
||||
"ordinary Coder must not install subject-scoped tools: {installed:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
Reference in New Issue
Block a user