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
@@ -66,6 +66,7 @@ fn requires_a_strict_nonempty_model_prefix() {
}
let security_risk = RolloutItem::SecurityRiskScore(SecurityRiskScore {
scores: BTreeMap::from([("action_risk".to_string(), 0.92)]),
sampled_at: None,
});
metadata_changed.push(security_risk.clone());
assert!(model_transcripts_match(&history, &metadata_changed));