@@ -19,7 +19,7 @@ impl RuntimeMode {
1919}
2020
2121/// Settings-backed hosted review configuration.
22- #[ derive( Debug , Clone , PartialEq , Eq , Serialize , Deserialize , Default ) ]
22+ #[ derive( Debug , Clone , PartialEq , Eq , Serialize , Deserialize ) ]
2323#[ serde( rename_all = "camelCase" ) ]
2424pub struct HostedReviewConfig {
2525 #[ serde( default , skip_serializing_if = "is_false" ) ]
@@ -34,6 +34,31 @@ pub struct HostedReviewConfig {
3434 pub allow_mcp_servers : bool ,
3535 #[ serde( default , skip_serializing_if = "is_false" ) ]
3636 pub allow_plugins : bool ,
37+ #[ serde( default , skip_serializing_if = "is_false" ) ]
38+ pub allow_auto_memory_persistence : bool ,
39+ #[ serde( default , skip_serializing_if = "MemorySourceTrust::is_unknown" ) ]
40+ pub memory_source_trust : MemorySourceTrust ,
41+ #[ serde(
42+ default = "default_memory_trust_threshold" ,
43+ skip_serializing_if = "is_default_memory_trust_threshold"
44+ ) ]
45+ pub memory_trust_threshold : MemorySourceTrust ,
46+ }
47+
48+ impl Default for HostedReviewConfig {
49+ fn default ( ) -> Self {
50+ Self {
51+ enabled : false ,
52+ allow_user_memory : false ,
53+ allow_managed_rules : false ,
54+ allow_write_tools : false ,
55+ allow_mcp_servers : false ,
56+ allow_plugins : false ,
57+ allow_auto_memory_persistence : false ,
58+ memory_source_trust : MemorySourceTrust :: Unknown ,
59+ memory_trust_threshold : default_memory_trust_threshold ( ) ,
60+ }
61+ }
3762}
3863
3964impl HostedReviewConfig {
@@ -44,9 +69,71 @@ impl HostedReviewConfig {
4469 && !self . allow_write_tools
4570 && !self . allow_mcp_servers
4671 && !self . allow_plugins
72+ && !self . allow_auto_memory_persistence
73+ && self . memory_source_trust == MemorySourceTrust :: Unknown
74+ && self . memory_trust_threshold == default_memory_trust_threshold ( )
75+ }
76+
77+ pub fn memory_source_trust ( & self ) -> MemorySourceTrust {
78+ self . memory_source_trust
79+ }
80+
81+ pub fn memory_trust_threshold ( & self ) -> MemorySourceTrust {
82+ self . memory_trust_threshold
83+ }
84+
85+ pub fn allows_auto_memory_persistence ( & self ) -> bool {
86+ self . allow_auto_memory_persistence
87+ && self
88+ . memory_source_trust
89+ . meets_threshold ( self . memory_trust_threshold )
90+ }
91+ }
92+
93+ /// Trust classification for the source that produced or approved memory.
94+ #[ derive( Debug , Clone , Copy , PartialEq , Eq , Serialize , Deserialize , Default ) ]
95+ #[ serde( rename_all = "kebab-case" ) ]
96+ pub enum MemorySourceTrust {
97+ SystemPolicy ,
98+ MaintainerApproved ,
99+ DefaultBranchCode ,
100+ ContributorInput ,
101+ ForkInput ,
102+ ModelInferred ,
103+ #[ default]
104+ Unknown ,
105+ }
106+
107+ impl MemorySourceTrust {
108+ pub fn is_unknown ( & self ) -> bool {
109+ matches ! ( self , Self :: Unknown )
110+ }
111+
112+ pub fn meets_threshold ( self , threshold : Self ) -> bool {
113+ self . rank ( ) >= threshold. rank ( )
114+ }
115+
116+ fn rank ( self ) -> u8 {
117+ match self {
118+ Self :: Unknown => 0 ,
119+ Self :: ForkInput => 10 ,
120+ Self :: ContributorInput => 20 ,
121+ Self :: ModelInferred => 30 ,
122+ Self :: DefaultBranchCode => 60 ,
123+ Self :: MaintainerApproved => 80 ,
124+ Self :: SystemPolicy => 100 ,
125+ }
47126 }
48127}
49128
129+ fn default_memory_trust_threshold ( ) -> MemorySourceTrust {
130+ MemorySourceTrust :: MaintainerApproved
131+ }
132+
133+ fn is_default_memory_trust_threshold ( value : & MemorySourceTrust ) -> bool {
134+ * value == default_memory_trust_threshold ( )
135+ }
136+
50137/// Canonical repository identity supplied by the hosted control plane or
51138/// derived from a git remote for local diagnostics.
52139#[ derive( Debug , Clone , PartialEq , Eq , Serialize , Deserialize ) ]
@@ -327,6 +414,31 @@ mod tests {
327414 ) ;
328415 }
329416
417+ #[ test]
418+ fn memory_source_trust_enforces_threshold_order ( ) {
419+ assert ! ( MemorySourceTrust :: MaintainerApproved
420+ . meets_threshold( MemorySourceTrust :: DefaultBranchCode ) ) ;
421+ assert ! ( !MemorySourceTrust :: ContributorInput
422+ . meets_threshold( MemorySourceTrust :: MaintainerApproved ) ) ;
423+ assert ! ( !MemorySourceTrust :: ForkInput . meets_threshold( MemorySourceTrust :: ContributorInput ) ) ;
424+ }
425+
426+ #[ test]
427+ fn hosted_memory_persistence_requires_explicit_trusted_policy ( ) {
428+ let mut config = HostedReviewConfig {
429+ enabled : true ,
430+ ..Default :: default ( )
431+ } ;
432+ assert ! ( !config. allows_auto_memory_persistence( ) ) ;
433+
434+ config. allow_auto_memory_persistence = true ;
435+ config. memory_source_trust = MemorySourceTrust :: ContributorInput ;
436+ assert ! ( !config. allows_auto_memory_persistence( ) ) ;
437+
438+ config. memory_source_trust = MemorySourceTrust :: MaintainerApproved ;
439+ assert ! ( config. allows_auto_memory_persistence( ) ) ;
440+ }
441+
330442 #[ test]
331443 fn security_private_domain_requires_explicit_public_review_allowance ( ) {
332444 assert ! ( !MemoryDomain :: SecurityPrivate . can_load_in_public_review( false ) ) ;
0 commit comments