@@ -81,7 +81,7 @@ impl Thresholds {
8181
8282/// The weight table: bias, per-rule weights, tier thresholds. Serialized as
8383/// JSON and shipped as data, not code.
84- #[ derive( Debug , Clone , Serialize , Deserialize ) ]
84+ #[ derive( Debug , Clone , PartialEq , Serialize , Deserialize ) ]
8585pub struct Weights {
8686 pub bias : f64 ,
8787 pub rules : BTreeMap < String , f64 > ,
@@ -153,6 +153,28 @@ impl Weights {
153153 serde_json:: from_str ( include_str ! ( "../weights/default.json" ) )
154154 . expect ( "embedded default weights must parse" )
155155 }
156+
157+ /// Fit timestamp from the provenance meta; None when the table
158+ /// carries none.
159+ pub fn fitted_at ( & self ) -> Option < chrono:: DateTime < chrono:: Utc > > {
160+ self . meta
161+ . as_ref ( ) ?
162+ . get ( "fitted_at" ) ?
163+ . as_str ( )
164+ . and_then ( |s| chrono:: DateTime :: parse_from_rfc3339 ( s) . ok ( ) )
165+ . map ( |t| t. with_timezone ( & chrono:: Utc ) )
166+ }
167+
168+ /// Whether this table is a strictly newer fit than `other`. A table
169+ /// without provenance is never newer, and any fitted table is newer
170+ /// than one without provenance.
171+ pub fn newer_fit_than ( & self , other : & Weights ) -> bool {
172+ match ( self . fitted_at ( ) , other. fitted_at ( ) ) {
173+ ( Some ( a) , Some ( b) ) => a > b,
174+ ( Some ( _) , None ) => true ,
175+ ( None , _) => false ,
176+ }
177+ }
156178}
157179
158180#[ cfg( test) ]
@@ -276,6 +298,24 @@ mod tests {
276298 assert ! ( t. bias < 0.0 , "prior must favor pass" ) ;
277299 }
278300
301+ #[ test]
302+ fn newer_fit_wins_and_missing_provenance_never_does ( ) {
303+ let stamped = |at : & str | Weights {
304+ meta : Some ( serde_json:: json!( { "fitted_at" : at } ) ) ,
305+ ..table ( )
306+ } ;
307+ let old = stamped ( "2026-08-06T21:24:28Z" ) ;
308+ let new = stamped ( "2026-08-10T00:00:25Z" ) ;
309+ assert ! ( new. newer_fit_than( & old) ) ;
310+ assert ! ( !old. newer_fit_than( & new) ) ;
311+ assert ! ( !old. newer_fit_than( & old) ) ;
312+ let bare = table ( ) ;
313+ assert ! ( old. newer_fit_than( & bare) ) ;
314+ assert ! ( !bare. newer_fit_than( & old) ) ;
315+ assert ! ( !bare. newer_fit_than( & bare) ) ;
316+ assert ! ( Weights :: default_table( ) . fitted_at( ) . is_some( ) ) ;
317+ }
318+
279319 #[ test]
280320 fn weights_serde_roundtrip ( ) {
281321 let t = table ( ) ;
0 commit comments