From 5549c50d86790c92b1d4773d0073bb6d6e47bb64 Mon Sep 17 00:00:00 2001 From: Hare Date: Sun, 14 Jun 2026 03:52:26 +0900 Subject: [PATCH] feat: remove feature host authority API --- crates/pod/src/feature.rs | 269 +-------------------- crates/pod/src/feature/builtin/task/mod.rs | 5 +- crates/pod/src/feature/builtin/ticket.rs | 29 +-- 3 files changed, 16 insertions(+), 287 deletions(-) diff --git a/crates/pod/src/feature.rs b/crates/pod/src/feature.rs index 5c903eb7..58a8ec86 100644 --- a/crates/pod/src/feature.rs +++ b/crates/pod/src/feature.rs @@ -1,8 +1,8 @@ //! Feature contribution registry for Pod-hosted builtin/plugin modules. //! //! This module defines the Pod-side feature boundary used to collect -//! descriptor metadata, host authority requests, tool contributions, safe hook -//! contributions, background task declarations, and service declarations before +//! descriptor metadata, tool contributions, safe hook contributions, background +//! task declarations, and service declarations before //! installing them into the existing Worker/HookRegistry host surfaces. //! //! The first implementation slice is intentionally host-mediated and @@ -69,26 +69,6 @@ pub enum FeatureRuntimeKind { ExternalPlugin, } -/// Host authority requested by a feature for host-mediated operations that can -/// cross sandbox or model-context boundaries. -/// -/// Contribution declarations such as tools, hooks, background tasks, and -/// services are descriptor/package-approved host-visible contributions, not -/// host authorities. Host authority grants are additive and do not replace -/// manifest/tool permission checks. -#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum HostAuthority { - Filesystem, - Network, - SecretRef { id: String }, - ModelNotification, - PodManagement, - StateStore { name: String }, - TicketBackend { root: String }, - ServiceAccess { service: ServiceId }, -} - /// A safe hook contribution point exposed to feature modules. #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] @@ -99,82 +79,6 @@ pub enum FeatureHookPoint { TurnEnd, } -/// Host authority request declared by a feature descriptor. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct HostAuthorityRequest { - pub authority: HostAuthority, - pub required: bool, - pub reason: String, -} - -impl HostAuthorityRequest { - pub fn required(authority: HostAuthority, reason: impl Into) -> Self { - Self { - authority, - required: true, - reason: reason.into(), - } - } - - pub fn optional(authority: HostAuthority, reason: impl Into) -> Self { - Self { - authority, - required: false, - reason: reason.into(), - } - } -} - -/// Host authority grants resolved by the host for one feature installation. -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] -pub struct HostAuthorityGrantSet { - granted: HashSet, - denied: Vec, -} - -impl HostAuthorityGrantSet { - pub fn grant_all(requests: &[HostAuthorityRequest]) -> Self { - Self { - granted: requests - .iter() - .map(|request| request.authority.clone()) - .collect(), - denied: Vec::new(), - } - } - - pub fn empty() -> Self { - Self::default() - } - - pub fn contains(&self, authority: &HostAuthority) -> bool { - self.granted.contains(authority) - } - - pub fn denied(&self) -> &[HostAuthorityDenial] { - &self.denied - } - - pub fn grant(&mut self, authority: HostAuthority) { - self.granted.insert(authority); - } - - pub fn deny(&mut self, authority: HostAuthority, reason: impl Into) { - self.granted.remove(&authority); - self.denied.push(HostAuthorityDenial { - authority, - reason: reason.into(), - }); - } -} - -/// Host-side denial of a requested feature host authority. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct HostAuthorityDenial { - pub authority: HostAuthority, - pub reason: String, -} - /// Serializable declaration of a tool contribution. The executable factory is /// carried by [`ToolContribution`] during installation. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -192,12 +96,10 @@ impl ToolDeclaration { } } -/// Executable tool contribution wrapper. Host-authority requirements are optional -/// per-tool gates for privileged host APIs, not permission to contribute a tool. +/// Executable tool contribution wrapper. pub struct ToolContribution { name: String, definition: ToolDefinition, - required_host_authorities: Vec, } impl ToolContribution { @@ -205,18 +107,9 @@ impl ToolContribution { Self { name: name.into(), definition, - required_host_authorities: Vec::new(), } } - pub fn with_required_host_authorities( - mut self, - required_host_authorities: Vec, - ) -> Self { - self.required_host_authorities = required_host_authorities; - self - } - pub fn name(&self) -> &str { &self.name } @@ -410,7 +303,6 @@ pub struct FeatureDescriptor { pub display_name: String, pub version: String, pub description: String, - pub requested_host_authorities: Vec, pub tools: Vec, pub hooks: Vec, pub background_tasks: Vec, @@ -426,7 +318,6 @@ impl FeatureDescriptor { display_name: display_name.into(), version: env!("CARGO_PKG_VERSION").into(), description: String::new(), - requested_host_authorities: Vec::new(), tools: Vec::new(), hooks: Vec::new(), background_tasks: Vec::new(), @@ -440,11 +331,6 @@ impl FeatureDescriptor { self } - pub fn with_host_authority(mut self, request: HostAuthorityRequest) -> Self { - self.requested_host_authorities.push(request); - self - } - pub fn with_tool(mut self, tool: ToolDeclaration) -> Self { self.tools.push(tool); self @@ -543,7 +429,6 @@ pub struct FeatureInstallReport { pub feature_id: FeatureId, pub runtime: FeatureRuntimeKind, pub installed: bool, - pub host_authority_grants: HostAuthorityGrantSet, pub installed_tools: Vec, pub installed_hooks: Vec, pub declared_background_tasks: Vec, @@ -554,12 +439,11 @@ pub struct FeatureInstallReport { } impl FeatureInstallReport { - fn new(descriptor: &FeatureDescriptor, host_authority_grants: HostAuthorityGrantSet) -> Self { + fn new(descriptor: &FeatureDescriptor) -> Self { Self { feature_id: descriptor.id.clone(), runtime: descriptor.runtime.clone(), installed: false, - host_authority_grants, installed_tools: Vec::new(), installed_hooks: Vec::new(), declared_background_tasks: Vec::new(), @@ -653,38 +537,14 @@ fn reject_undeclared_contribution( error } -fn require_host_authority( - host_authority_grants: &HostAuthorityGrantSet, - report: &mut FeatureInstallReport, - kind: FeatureContributionKind, - name: impl Into, - authority: &HostAuthority, -) -> Result<(), FeatureInstallError> { - if host_authority_grants.contains(authority) { - return Ok(()); - } - - let reason = format!("required host authority was not granted: {authority:?}"); - report.mark_skipped(kind, name, reason.clone()); - Err(FeatureInstallError::HostAuthorityDenied(reason)) -} - /// Model-visible durable notification sink skeleton. The first slice exposes /// the boundary without implementing a new event channel. pub struct FeatureNotificationSink<'a> { - host_authority_grants: &'a HostAuthorityGrantSet, report: &'a mut FeatureInstallReport, } impl FeatureNotificationSink<'_> { pub fn notify_model(&mut self, message: impl Into) -> Result<(), FeatureInstallError> { - require_host_authority( - self.host_authority_grants, - self.report, - FeatureContributionKind::Notification, - "notify_model", - &HostAuthority::ModelNotification, - )?; let message = message.into(); self.report.diagnostics.push(FeatureDiagnostic::warning(format!( "model notification requested during feature installation but no durable Notify host is attached: {message}" @@ -744,7 +604,6 @@ impl FeatureDiagnosticSink<'_> { pub struct ToolContributionRegistrar<'a> { feature_id: &'a FeatureId, declarations: &'a FeatureContributionDeclarations, - host_authority_grants: &'a HostAuthorityGrantSet, pending_tools: &'a mut Vec, installed_tool_names: &'a mut HashMap, report: &'a mut FeatureInstallReport, @@ -776,16 +635,6 @@ impl ToolContributionRegistrar<'_> { )); } - for authority in &contribution.required_host_authorities { - require_host_authority( - self.host_authority_grants, - self.report, - FeatureContributionKind::Tool, - model_visible_name.clone(), - authority, - )?; - } - if let Some(first) = self.installed_tool_names.get(&model_visible_name) { let error = FeatureInstallError::DuplicateToolName { tool: model_visible_name.clone(), @@ -951,7 +800,6 @@ impl FeatureServiceRegistrar<'_> { pub struct FeatureInstallContext<'a> { feature_id: &'a FeatureId, declarations: &'a FeatureContributionDeclarations, - host_authority_grants: &'a HostAuthorityGrantSet, pending_tools: &'a mut Vec, installed_tool_names: &'a mut HashMap, hook_builder: &'a mut HookRegistryBuilder, @@ -964,15 +812,10 @@ impl FeatureInstallContext<'_> { self.feature_id } - pub fn host_authority_grants(&self) -> &HostAuthorityGrantSet { - self.host_authority_grants - } - pub fn tools(&mut self) -> ToolContributionRegistrar<'_> { ToolContributionRegistrar { feature_id: self.feature_id, declarations: self.declarations, - host_authority_grants: self.host_authority_grants, pending_tools: self.pending_tools, installed_tool_names: self.installed_tool_names, report: self.report, @@ -1007,7 +850,6 @@ impl FeatureInstallContext<'_> { pub fn notifications(&mut self) -> FeatureNotificationSink<'_> { FeatureNotificationSink { - host_authority_grants: self.host_authority_grants, report: self.report, } } @@ -1107,10 +949,8 @@ impl FeatureRegistryBuilder { let mut seen_features = HashSet::new(); for (module, descriptor) in self.modules.into_iter().zip(descriptors.into_iter()) { - let host_authority_grants = - HostAuthorityGrantSet::grant_all(&descriptor.requested_host_authorities); let declarations = FeatureContributionDeclarations::from_descriptor(&descriptor); - let mut report = FeatureInstallReport::new(&descriptor, host_authority_grants.clone()); + let mut report = FeatureInstallReport::new(&descriptor); if !seen_features.insert(descriptor.id.clone()) { report.diagnostics.push(FeatureDiagnostic::error(format!( @@ -1126,13 +966,6 @@ impl FeatureRegistryBuilder { continue; } - for authority in host_authority_grants.denied() { - report.diagnostics.push(FeatureDiagnostic::warning(format!( - "host authority denied: {:?}: {}", - authority.authority, authority.reason - ))); - } - let mut required_service_failed = false; for requirement in descriptor.requires_services.iter().cloned() { if service_registry.provides(&requirement.id) { @@ -1192,7 +1025,6 @@ impl FeatureRegistryBuilder { let mut context = FeatureInstallContext { feature_id: &descriptor.id, declarations: &declarations, - host_authority_grants: &host_authority_grants, pending_tools, installed_tool_names: &mut installed_tool_names, hook_builder, @@ -1256,8 +1088,6 @@ pub enum FeatureInstallError { first_feature: String, duplicate_feature: String, }, - #[error("feature host authority denied: {0}")] - HostAuthorityDenied(String), #[error("feature install failed: {0}")] Install(String), } @@ -1335,7 +1165,7 @@ mod tests { } #[test] - fn descriptor_contributions_and_empty_host_authority_grants_are_recorded() { + fn descriptor_contributions_are_recorded() { let descriptor = FeatureDescriptor::builtin("dummy", "Dummy") .with_tool(ToolDeclaration::new("Dummy", "dummy tool")) .with_background_task(BackgroundTaskDeclaration::descriptor_only( @@ -1358,7 +1188,6 @@ mod tests { assert!(feature_report.installed); assert_eq!(feature_report.installed_tools, vec!["Dummy"]); assert_eq!(feature_report.declared_background_tasks[0].name, "daily"); - assert!(feature_report.host_authority_grants.denied().is_empty()); } #[test] @@ -1421,79 +1250,6 @@ mod tests { assert_eq!(report.reports[0].skipped[0].name, "Actual"); } - #[test] - fn tool_host_authority_requirements_use_host_authority_grants_not_contribution_declarations() { - struct HostAuthorityToolFeature { - descriptor: FeatureDescriptor, - required_host_authorities: Vec, - } - - impl FeatureModule for HostAuthorityToolFeature { - fn descriptor(&self) -> FeatureDescriptor { - self.descriptor.clone() - } - - fn install( - &self, - context: &mut FeatureInstallContext<'_>, - ) -> Result<(), FeatureInstallError> { - context.tools().register( - ToolContribution::new("NetworkTool", dummy_tool("NetworkTool")) - .with_required_host_authorities(self.required_host_authorities.clone()), - ) - } - } - - let mut hook_builder = HookRegistryBuilder::default(); - let mut pending_tools = Vec::new(); - let missing_grant = FeatureDescriptor::builtin("missing-host-authority", "Missing") - .with_tool(ToolDeclaration::new("NetworkTool", "network host API tool")); - let missing_report = FeatureRegistryBuilder::new() - .with_module(HostAuthorityToolFeature { - descriptor: missing_grant, - required_host_authorities: vec![HostAuthority::Network], - }) - .install_into_pending(&mut pending_tools, &mut hook_builder); - - assert!(pending_tools.is_empty()); - assert!(!missing_report.reports[0].installed); - assert!( - missing_report.reports[0] - .diagnostics - .iter() - .any(|diagnostic| { - diagnostic - .message - .contains("required host authority was not granted") - }) - ); - assert_eq!( - missing_report.reports[0].skipped[0].kind, - FeatureContributionKind::Tool - ); - - let granted = FeatureDescriptor::builtin("granted-host-authority", "Granted") - .with_host_authority(HostAuthorityRequest::required( - HostAuthority::Network, - "uses a host network API", - )) - .with_tool(ToolDeclaration::new("NetworkTool", "network host API tool")); - let granted_report = FeatureRegistryBuilder::new() - .with_module(HostAuthorityToolFeature { - descriptor: granted, - required_host_authorities: vec![HostAuthority::Network], - }) - .install_into_pending(&mut pending_tools, &mut hook_builder); - - assert!(granted_report.reports[0].installed); - assert!( - granted_report.reports[0] - .host_authority_grants - .contains(&HostAuthority::Network) - ); - assert_eq!(pending_tools.len(), 1); - } - #[test] fn stateful_tool_definition_is_materialized_once_for_report_and_worker() { struct StatefulToolFeature { @@ -1790,7 +1546,7 @@ mod tests { } #[test] - fn background_task_declaration_is_not_host_authority_gated() { + fn background_task_declaration_is_descriptor_contribution() { let descriptor = FeatureDescriptor::builtin("background", "Background") .with_background_task(BackgroundTaskDeclaration::descriptor_only( "declared-task", @@ -1811,7 +1567,7 @@ mod tests { } #[test] - fn service_provider_declaration_is_not_host_authority_gated() { + fn service_provider_declaration_is_descriptor_contribution() { let service = ServiceId::builtin("declared-service"); let descriptor = FeatureDescriptor::builtin("service", "Service").with_provided_service( ServiceDeclaration::new(service.clone(), "1", "descriptor contribution"), @@ -1829,7 +1585,7 @@ mod tests { } #[test] - fn builtin_internal_task_feature_descriptor_has_exact_tools_hooks_and_no_host_authorities() { + fn builtin_internal_task_feature_descriptor_has_exact_tools_hooks() { let descriptor = builtin::task_tools_feature().descriptor(); let tool_names: Vec<_> = descriptor .tools @@ -1845,7 +1601,6 @@ mod tests { assert_eq!(descriptor.id.as_str(), "builtin:task-tools"); assert_eq!(descriptor.runtime, FeatureRuntimeKind::Builtin); - assert!(descriptor.requested_host_authorities.is_empty()); assert_eq!( hook_points, vec![FeatureHookPoint::PreRequest, FeatureHookPoint::PreToolCall] @@ -1860,7 +1615,7 @@ mod tests { } #[test] - fn builtin_internal_task_feature_installs_declared_tools_without_host_authorities() { + fn builtin_internal_task_feature_installs_declared_tools() { let mut hook_builder = HookRegistryBuilder::default(); let mut pending_tools = Vec::new(); let mut builder = FeatureRegistryBuilder::new(); @@ -1882,10 +1637,6 @@ mod tests { assert_eq!(report.reports.len(), 1); assert!(report.reports[0].installed); - assert_eq!( - report.reports[0].host_authority_grants, - HostAuthorityGrantSet::empty() - ); assert!(report.reports[0].skipped.is_empty()); assert!(report.reports[0].diagnostics.is_empty()); assert_eq!(report.reports[0].installed_hooks.len(), 2); diff --git a/crates/pod/src/feature/builtin/task/mod.rs b/crates/pod/src/feature/builtin/task/mod.rs index d6d8c725..abf6741d 100644 --- a/crates/pod/src/feature/builtin/task/mod.rs +++ b/crates/pod/src/feature/builtin/task/mod.rs @@ -35,9 +35,8 @@ const TASK_MANAGEMENT_TOOL_NAMES: [&str; 2] = ["TaskCreate", "TaskUpdate"]; /// /// The returned module contributes `TaskCreate`, `TaskUpdate`, `TaskGet`, and /// `TaskList` through descriptor-approved tool registration, plus built-in hooks -/// that maintain Task-reminder state. It does not request sandbox/external-plugin -/// host authorities; normal ToolRegistry and PreToolCall permission policy still -/// applies at call time. +/// that maintain Task-reminder state. Normal ToolRegistry and PreToolCall +/// permission policy still applies at call time. pub fn task_tools_feature() -> TaskFeature { TaskFeature::new() } diff --git a/crates/pod/src/feature/builtin/ticket.rs b/crates/pod/src/feature/builtin/ticket.rs index f9cf2f05..c3bfd804 100644 --- a/crates/pod/src/feature/builtin/ticket.rs +++ b/crates/pod/src/feature/builtin/ticket.rs @@ -18,14 +18,13 @@ use ticket::{ use crate::feature::{ FeatureDescriptor, FeatureDiagnostic, FeatureInstallContext, FeatureInstallError, - FeatureModule, HostAuthority, HostAuthorityRequest, ToolContribution, ToolDeclaration, + FeatureModule, ToolContribution, ToolDeclaration, }; const FEATURE_ID: &str = "ticket"; const FEATURE_NAME: &str = "Ticket tools"; const FEATURE_DESCRIPTION: &str = "Typed local Ticket work-item operations over a bounded backend root. \ The tools operate through the ticket crate backend and do not grant generic filesystem write scope."; -const AUTHORITY_REASON: &str = "Use a configured local Ticket backend root for typed work-item operations without generic filesystem write authority."; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum TicketFeatureAccess { @@ -150,12 +149,6 @@ impl TicketFeature { names } - fn authority(&self) -> HostAuthority { - HostAuthority::TicketBackend { - root: self.backend_root.display().to_string(), - } - } - fn usable_backend_root(&self) -> Result { let root = self .backend_root @@ -171,11 +164,7 @@ impl TicketFeature { impl FeatureModule for TicketFeature { fn descriptor(&self) -> FeatureDescriptor { let mut descriptor = FeatureDescriptor::builtin(FEATURE_ID, FEATURE_NAME) - .with_description(FEATURE_DESCRIPTION) - .with_host_authority(HostAuthorityRequest::required( - self.authority(), - AUTHORITY_REASON, - )); + .with_description(FEATURE_DESCRIPTION); let enabled_tool_names = self.enabled_tool_names(); for name in &enabled_tool_names { descriptor = descriptor.with_tool(ToolDeclaration::new( @@ -207,7 +196,6 @@ impl FeatureModule for TicketFeature { return Ok(()); } }; - let authority = self.authority(); let backend = LocalTicketBackend::new(usable_root) .with_record_language(self.record_language.as_deref()); let allowed_tool_names = self.enabled_tool_names(); @@ -221,10 +209,7 @@ impl FeatureModule for TicketFeature { { continue; } - tools.register( - ToolContribution::new(name, definition) - .with_required_host_authorities(vec![authority.clone()]), - )?; + tools.register(ToolContribution::new(name, definition))?; } Ok(()) } @@ -284,7 +269,7 @@ mod tests { } #[test] - fn descriptor_declares_ticket_tools_and_backend_authority() { + fn descriptor_declares_ticket_tools() { let temp = TempDir::new().unwrap(); let feature = ticket_tools_feature(temp.path()); let descriptor = feature.descriptor(); @@ -299,11 +284,6 @@ mod tests { .collect::>(), TICKET_TOOL_NAMES ); - assert_eq!(descriptor.requested_host_authorities.len(), 1); - assert!(matches!( - descriptor.requested_host_authorities[0].authority, - HostAuthority::TicketBackend { .. } - )); } #[test] @@ -321,7 +301,6 @@ mod tests { .collect::>(), TICKET_READ_ONLY_TOOL_NAMES ); - assert_eq!(descriptor.requested_host_authorities.len(), 1); } #[test]