Keep the latest Guardian risk score during concurrent sampling (#38580)

## Why

Concurrent Guardian samples can finish out of order, allowing an older sample to
replace the thread's newer in-memory security risk score.

## What changed

- Record when each `SecurityRiskScore` sample starts while preserving compatibility
  with records that have no timestamp.
- Add `ExtensionData::insert_if` so checking and replacing a typed attachment happen
  under the same lock.
- Update Guardian V2 to replace the thread's risk score only when the completed sample
  is newer than the stored one.

## Testing

- Verify concurrent conditional inserts retain the newest value.
- Verify Guardian-generated risk scores include a sampling timestamp.

GitOrigin-RevId: 6543a4e5497da4c623331050789e91613f931c4b
This commit is contained in:
jif
2026-08-14 13:44:51 +00:00
committed by copyberry
parent 42d842a91a
commit 2a452d7dc1
11 changed files with 76 additions and 2 deletions
+1
View File
@@ -443,6 +443,7 @@ async fn load_rollout_items_preserves_security_risk_scores() -> std::io::Result<
("action_risk".to_string(), 0.76),
("data_exfiltration".to_string(), 0.31),
]),
sampled_at: None,
};
let security_risk_item = RolloutItem::SecurityRiskScore(security_risk.clone());
for history_mode in [ThreadHistoryMode::Legacy, ThreadHistoryMode::Paginated] {