diff --git a/docs/agent-environments.md b/docs/agent-environments.md index 33647e198d..5d46cddf56 100644 --- a/docs/agent-environments.md +++ b/docs/agent-environments.md @@ -125,8 +125,9 @@ the replayed body is the bytes the browser exposes for each response, with any declared secret replaced by its redaction marker (a body that redaction had to empty replays no body). -Each record takes its start time from when the scenario observed the request -and its duration from the browser's timing, so the state orders and overlaps +Each record takes its start and duration from the browser's timing (the time +the scenario observed the request where the browser reported no start), so the +state orders and overlaps records as it does a HAR's and learns a write clock: an update the application stamps with a modification time can be served. A scenario captured before these times were recorded has none; its records keep request order and the diff --git a/docs/browser-scenario-contract.md b/docs/browser-scenario-contract.md index 8e432a9642..0ac55fab20 100644 --- a/docs/browser-scenario-contract.md +++ b/docs/browser-scenario-contract.md @@ -356,13 +356,16 @@ All three options default to `false`. Any selected network content also enables network metadata observation; listing `network` in `events` is then optional. Each observed request object receives a capture-scoped `transaction_id`. Each `request` event carries `observed_at`, the wall-clock time REA observed the -request start (a few milliseconds after the browser started it), and each -`request-finished` or `request-failed` event carries `elapsed_ms`, the browser's -own time from the request start to the end of its response, when the browser -reports one. The environment views time scenario records from these, as HAR -entries are timed; captures made before these fields existed lack them and are -read in record order. Scenario comparisons ignore both fields -(`ignore_network_request_timing`). +request start. Playwright reports the browser's own start only once a request +ends, so each `request-finished` or `request-failed` event carries `started_at`, +the browser's wall-clock start, and `elapsed_ms`, its time from that start to +the end of the response, when the browser reports them. The environment views +time scenario records from these, as HAR entries are timed: the browser's start +where it reported one, else `observed_at`, which trails it by up to tens of +milliseconds while REA reads other responses, and a duration of 0 where the +browser reported none. Captures made before these fields existed lack them and +are read in record order. REA versions before them refuse captures that carry +them. Scenario comparisons ignore all three (`ignore_network_request_timing`). Requests sharing a URL remain distinct. Redirects link to the preceding transaction only when it was observed; a null predecessor does not establish that no earlier hop existed. Request events require both transaction fields; @@ -460,8 +463,8 @@ and compared as an unordered collection after caller rules apply, because concurrent responses arrive in any order and can land in a later step; a redirect retains the complete predecessor ancestry, including each hop's own request, response outcome, and selected network content. Each transaction's -capture-local IDs, sequence/index fields, and content source-sequence anchor -are removed. Caller rules normalize retained strings before hashing. The +capture-local IDs, sequence/index fields, content source-sequence anchor and +request times (`observed_at`, `started_at`, `elapsed_ms`) are removed. Caller rules normalize retained strings before hashing. The `preserve_redirect_transaction_ancestry` built-in rule fingerprints each hop as canonical SHA-256 of `{ events, redirected_from }`: `events` is the sorted list of its normalized event SHA-256 digests, and `redirected_from` is its @@ -482,7 +485,10 @@ scope/flags, value fingerprints, and duplicate entries even when redaction or literal rules make names collide. Captured storage arrays retain their original order. The `compare_storage_entries_as_unordered_collection` built-in rule participates in the normalization commitment; new storage comparison digests -therefore differ from earlier order-sensitive comparisons. +therefore differ from earlier order-sensitive comparisons. The +`ignore_network_request_timing` built-in rule likewise joins the commitment, so +normalization digests differ from comparisons made before it, though captures +with and without request times compare alike. Each aligned step reports changed, unchanged, or unknown. Changed and unknown action, screenshot, DOM, accessibility, URL, history, storage, and event diff --git a/docs/specs/agent-environments/scenario-timing/plan.md b/docs/specs/agent-environments/scenario-timing/plan.md index 222ed2d6f7..ac35b80b86 100644 --- a/docs/specs/agent-environments/scenario-timing/plan.md +++ b/docs/specs/agent-environments/scenario-timing/plan.md @@ -1,19 +1,22 @@ # Plan -1. Schema: optional `observed_at` (ISO date-time) on `request` events and - `elapsed_ms` (non-negative number) on `request-finished` and - `request-failed` events. -2. Collector: stamp `observed_at` from an injectable wall clock when a request - is observed, admitting it with the other metadata strings, and copy the - browser's `timing().responseEnd` into `elapsed_ms` when it is known. -3. Records: index each transaction's finished or failed duration and give each - attributable record `startedDateTime` and `time`; add a limitation when any - record is timed. +1. Schema: optional `observed_at` (ISO date-time) on `request` events, and + `started_at` (ISO date-time) and `elapsed_ms` (non-negative number) on + `request-finished` and `request-failed` events. +2. Collector: stamp `observed_at` when a request is observed and copy the + browser's `timing().startTime` and `responseEnd` into `started_at` and + `elapsed_ms` when it reports them, admitting the strings with the other + metadata. +3. Records: index each transaction's single finished or failed start and + duration and give each attributable record `startedDateTime` and `time`; + add a limitation when any record is timed. 4. Comparison: drop both fields from network evidence before fingerprinting and commit the rule in the built-in list and the comparison limitation. 5. Docs: the scenario contract's network evidence and the environment docs' browser scenario section. 6. Tests: the collector stamps both fields, records take them, the state learns the HAR's clock and verdicts from a timed scenario and none from an untimed - one, comparisons ignore them, and the real-Chrome network lane checks every - request and finished response is timed. + or partly untimed one, a request that never ended or ended twice falls back + to its observation, comparisons ignore the times (also against an untimed + capture), and the real-Chrome network lane checks every request is observed + and every finished request carries the browser's start and duration. diff --git a/docs/specs/agent-environments/scenario-timing/spec.md b/docs/specs/agent-environments/scenario-timing/spec.md index 87c265e011..ff3261ad16 100644 --- a/docs/specs/agent-environments/scenario-timing/spec.md +++ b/docs/specs/agent-environments/scenario-timing/spec.md @@ -16,17 +16,20 @@ scenario capture learns the clock a HAR does. ## Requirements - Each `request` event carries `observed_at`, the ISO wall-clock time the - provider observed the request start; Playwright reports no start time until - the response. -- Each `request-finished` and `request-failed` event carries `elapsed_ms`, the - browser's time from the request start to the end of its response, when the - browser reports one (`timing().responseEnd >= 0`). -- Both fields are optional in the schema, so Evidence captured before them - still parses and is read exactly as before. -- Scenario records take `startedDateTime` from `observed_at` and `time` from the - transaction's single finished or failed event (0 without one), and say so in - a limitation; a capture whose requests all carry `observed_at` gets the - recorded-start-time timeline and a write clock. + provider observed the request start; Playwright reports the browser's start + only once a request ends. +- Each `request-finished` and `request-failed` event carries `started_at`, the + browser's wall-clock start (`timing().startTime > 0`), and `elapsed_ms`, its + time from that start to the end of the response (`timing().responseEnd >= 0`), + when the browser reports them. +- The fields are optional in the schema, so Evidence captured before them still + parses and is read exactly as before. REA versions before them refuse + Evidence that carries them. +- Scenario records take `startedDateTime` from the transaction's single + finished or failed event's `started_at`, else from `observed_at`, and `time` + from its `elapsed_ms` (0 without one), and say so in a limitation; a capture + whose requests are all timed gets the recorded-start-time timeline and a + write clock. - Scenario comparisons ignore both fields, as a committed built-in rule (`ignore_network_request_timing`). - The added metadata is charged to the event budgets as other metadata is. @@ -36,5 +39,6 @@ scenario capture learns the clock a HAR does. - No change to HAR captures, to how the state uses times, or to what a record's timing means for overlap or clock rules. - No retroactive timing of captures made before this change. -- No browser start times from response timing events: `observed_at` is the - provider's observation, a few milliseconds after the browser started. +- No browser start for a request that never ended: its record starts at + `observed_at`, which can trail the browser's start by tens of milliseconds + while the provider reads other responses, with no duration.