[codex-analytics] plumb protocol-native review timing (#21434)

## Why

We want terminal tool review analytics, but the reducer should not stamp
review timing from its own wall clock.

This PR plumbs review timing through the real protocol and app-server
seams so downstream analytics can consume the emitter's timestamps
directly. Guardian reviews keep their enriched `started_at` /
`completed_at` analytics fields by deriving those legacy second-based
values from the same protocol-native millisecond lifecycle timestamps,
rather than sampling a separate analytics clock.

## What changed

- add `started_at_ms` to user approval request payloads
- add `started_at_ms` / `completed_at_ms` to guardian review
notifications
- preserve Guardian review `started_at` / `completed_at` enrichment from
the protocol-native timing source
- stamp typed `ServerResponse` analytics facts with app-server-observed
`completed_at_ms`
- thread the new timing fields through core, protocol, app-server, TUI,
and analytics fixtures

## Verification

- `cargo test -p codex-app-server outgoing_message --manifest-path
codex-rs/Cargo.toml`
- `cargo test -p codex-app-server-protocol guardian --manifest-path
codex-rs/Cargo.toml`
- `cargo test -p codex-tui guardian --manifest-path codex-rs/Cargo.toml`
- `cargo test -p codex-analytics analytics_client_tests --manifest-path
codex-rs/Cargo.toml`

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/21434).
* #18748
* __->__ #21434
* #18747
* #17090
* #17089
* #20514
This commit is contained in:
rhan-oai
2026-05-07 20:31:41 -07:00
committed by GitHub
parent af16baa549
commit 99016ec732
44 changed files with 384 additions and 9 deletions
@@ -2967,6 +2967,7 @@ mod tests {
thread_id: "thr_123".to_string(),
turn_id: "turn_123".to_string(),
item_id: "call_123".to_string(),
started_at_ms: 0,
approval_id: None,
reason: None,
network_approval_context: None,
@@ -243,6 +243,7 @@ pub fn guardian_auto_approval_review_notification(
thread_id: conversation_id.to_string(),
turn_id,
review_id: assessment.id.clone(),
started_at_ms: assessment.started_at_ms,
target_item_id: assessment.target_item_id.clone(),
review,
action,
@@ -258,6 +259,10 @@ pub fn guardian_auto_approval_review_notification(
thread_id: conversation_id.to_string(),
turn_id,
review_id: assessment.id.clone(),
started_at_ms: assessment.started_at_ms,
completed_at_ms: assessment
.completed_at_ms
.unwrap_or(assessment.started_at_ms),
target_item_id: assessment.target_item_id.clone(),
decision_source: assessment
.decision_source
@@ -2143,6 +2143,8 @@ mod tests {
id: "review-guardian-exec".into(),
target_item_id: Some("guardian-exec".into()),
turn_id: "turn-1".into(),
started_at_ms: 1_000,
completed_at_ms: None,
status: GuardianAssessmentStatus::InProgress,
risk_level: None,
user_authorization: None,
@@ -2160,6 +2162,8 @@ mod tests {
id: "review-guardian-exec".into(),
target_item_id: Some("guardian-exec".into()),
turn_id: "turn-1".into(),
started_at_ms: 1_000,
completed_at_ms: Some(1_042),
status: GuardianAssessmentStatus::Denied,
risk_level: Some(codex_protocol::protocol::GuardianRiskLevel::High),
user_authorization: Some(codex_protocol::protocol::GuardianUserAuthorization::Low),
@@ -2222,6 +2226,8 @@ mod tests {
id: "review-guardian-execve".into(),
target_item_id: Some("guardian-execve".into()),
turn_id: "turn-1".into(),
started_at_ms: 2_000,
completed_at_ms: None,
status: GuardianAssessmentStatus::InProgress,
risk_level: None,
user_authorization: None,
@@ -2525,6 +2531,7 @@ mod tests {
EventMsg::ApplyPatchApprovalRequest(ApplyPatchApprovalRequestEvent {
call_id: "patch-call".into(),
turn_id: turn_id.to_string(),
started_at_ms: 0,
changes: [(
PathBuf::from("README.md"),
codex_protocol::protocol::FileChange::Add {
@@ -1073,6 +1073,9 @@ pub struct ItemStartedNotification {
pub struct ItemGuardianApprovalReviewStartedNotification {
pub thread_id: String,
pub turn_id: String,
/// Unix timestamp (in milliseconds) when this review started.
#[ts(type = "number")]
pub started_at_ms: i64,
/// Stable identifier for this review.
pub review_id: String,
/// Identifier for the reviewed item or tool call when one exists.
@@ -1099,6 +1102,12 @@ pub struct ItemGuardianApprovalReviewStartedNotification {
pub struct ItemGuardianApprovalReviewCompletedNotification {
pub thread_id: String,
pub turn_id: String,
/// Unix timestamp (in milliseconds) when this review started.
#[ts(type = "number")]
pub started_at_ms: i64,
/// Unix timestamp (in milliseconds) when this review completed.
#[ts(type = "number")]
pub completed_at_ms: i64,
/// Stable identifier for this review.
pub review_id: String,
/// Identifier for the reviewed item or tool call when one exists.
@@ -1248,6 +1257,9 @@ pub struct CommandExecutionRequestApprovalParams {
pub thread_id: String,
pub turn_id: String,
pub item_id: String,
/// Unix timestamp (in milliseconds) when this approval request started.
#[ts(type = "number")]
pub started_at_ms: i64,
/// Unique identifier for this specific approval callback.
///
/// For regular shell/unified_exec approvals, this is null.
@@ -1321,6 +1333,9 @@ pub struct FileChangeRequestApprovalParams {
pub thread_id: String,
pub turn_id: String,
pub item_id: String,
/// Unix timestamp (in milliseconds) when this approval request started.
#[ts(type = "number")]
pub started_at_ms: i64,
/// Optional explanatory reason (e.g. request for extra write access).
#[ts(optional = nullable)]
pub reason: Option<String>,
@@ -826,6 +826,9 @@ pub struct PermissionsRequestApprovalParams {
pub thread_id: String,
pub turn_id: String,
pub item_id: String,
/// Unix timestamp (in milliseconds) when this approval request started.
#[ts(type = "number")]
pub started_at_ms: i64,
pub cwd: AbsolutePathBuf,
pub reason: Option<String>,
pub permissions: RequestPermissionProfile,
@@ -277,6 +277,7 @@ fn command_execution_request_approval_rejects_relative_additional_permission_pat
"threadId": "thr_123",
"turnId": "turn_123",
"itemId": "call_123",
"startedAtMs": 1,
"command": "cat file",
"cwd": absolute_path_string("tmp"),
"commandActions": null,
@@ -317,6 +318,7 @@ fn permissions_request_approval_uses_request_permission_profile() {
"threadId": "thr_123",
"turnId": "turn_123",
"itemId": "call_123",
"startedAtMs": 1,
"cwd": absolute_path_string("repo"),
"reason": "Select a workspace root",
"permissions": {
@@ -379,6 +381,7 @@ fn permissions_request_approval_rejects_macos_permissions() {
"threadId": "thr_123",
"turnId": "turn_123",
"itemId": "call_123",
"startedAtMs": 1,
"cwd": absolute_path_string("repo"),
"reason": "Select a workspace root",
"permissions": {