@@ -112,7 +112,7 @@ use tonic::{Code, Request, Response, Status};
112112use tracing::{debug, info, warn};
113113
114114use super::validation::{
115- level_matches, source_matches, validate_and_canonicalize_policy, validate_annotations ,
115+ level_matches, source_matches, validate_and_canonicalize_policy, validate_caller_annotations ,
116116 validate_no_reserved_provider_policy_keys, validate_policy_safety,
117117 validate_static_fields_unchanged,
118118};
@@ -4167,7 +4167,7 @@ async fn handle_update_config_inner(
41674167) -> Result<Response<UpdateConfigResponse>, Status> {
41684168 let replay_facts = super::mutation_replay::ordinary::Facts::from_request(&request);
41694169 let req = request.into_inner();
4170- validate_annotations (&req.annotations, "annotations")?;
4170+ validate_caller_annotations (&req.annotations, "annotations")?;
41714171 let sandbox = if req.global {
41724172 if !req.sandbox.is_empty() || req.workspace_scope.is_some() {
41734173 return Err(Status::invalid_argument(
@@ -24751,6 +24751,49 @@ mod tests {
2475124751 assert!(err.message().contains("label key"));
2475224752 }
2475324753
24754+ #[tokio::test]
24755+ async fn update_config_rejects_reserved_annotations() {
24756+ let state = test_server_state().await;
24757+ state
24758+ .store
24759+ .put_message(&test_sandbox(
24760+ "sb-reserved-annotation",
24761+ "reserved-annotation",
24762+ ProtoSandboxPolicy::default(),
24763+ Vec::new(),
24764+ ))
24765+ .await
24766+ .unwrap();
24767+
24768+ let err = handle_update_config(
24769+ &state,
24770+ with_user(Request::new(UpdateConfigRequest {
24771+ sandbox: "reserved-annotation".to_string(),
24772+ workspace_scope: Some(openshell_core::proto::workspace_selector(
24773+ "default".to_string(),
24774+ )),
24775+ policy: Some(ProtoSandboxPolicy::default()),
24776+ annotations: HashMap::from([(
24777+ "internal.openshell.ai/auth-epoch".to_string(),
24778+ "1".to_string(),
24779+ )]),
24780+ ..Default::default()
24781+ })),
24782+ )
24783+ .await
24784+ .unwrap_err();
24785+
24786+ assert_eq!(err.code(), Code::InvalidArgument);
24787+ assert!(err.message().contains("internal.openshell.ai/"));
24788+ let stored = state
24789+ .store
24790+ .get_message_by_name::<Sandbox>("default", "reserved-annotation")
24791+ .await
24792+ .unwrap()
24793+ .unwrap();
24794+ assert!(stored.metadata.unwrap().annotations.is_empty());
24795+ }
24796+
2475424797 #[tokio::test]
2475524798 async fn update_config_user_policy_rejects_reserved_provider_key() {
2475624799 let state = test_server_state().await;
0 commit comments