[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
+10
View File
@@ -187,6 +187,12 @@ pub struct GuardianAssessmentEvent {
/// Uses `#[serde(default)]` for backwards compatibility.
#[serde(default)]
pub turn_id: String,
#[serde(default)]
#[ts(type = "number")]
pub started_at_ms: i64,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional, type = "number")]
pub completed_at_ms: Option<i64>,
pub status: GuardianAssessmentStatus,
/// Coarse risk label. Omitted while the assessment is in progress.
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -223,6 +229,8 @@ pub struct ExecApprovalRequestEvent {
/// Uses `#[serde(default)]` for backwards compatibility.
#[serde(default)]
pub turn_id: String,
#[ts(type = "number")]
pub started_at_ms: i64,
/// The command to be executed.
pub command: Vec<String>,
/// The command's working directory.
@@ -370,6 +378,8 @@ pub struct ApplyPatchApprovalRequestEvent {
/// Uses `#[serde(default)]` for backwards compatibility with older senders.
#[serde(default)]
pub turn_id: String,
#[ts(type = "number")]
pub started_at_ms: i64,
pub changes: HashMap<PathBuf, FileChange>,
/// Optional explanatory reason (e.g. request for extra write access).
#[serde(skip_serializing_if = "Option::is_none")]
@@ -71,6 +71,8 @@ pub struct RequestPermissionsEvent {
/// Uses `#[serde(default)]` for backwards compatibility.
#[serde(default)]
pub turn_id: String,
#[ts(type = "number")]
pub started_at_ms: i64,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
pub permissions: RequestPermissionProfile,