[PR #6373] [CLOSED] fix(browser): record session load failures only for main-frame documents #7318

Closed
opened 2026-11-01 21:47:00 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/6373
Author: @Adolanium
Created: 10/22/2026
Status: ❌ Closed

Base: main ← Head: fix/observe-session-load-failures


📝 Commits (10+)

  • dbd8aa3 fix(browser): record session load failures only for main-frame documents
  • 1e48122 Merge upstream/main into fix/observe-session-load-failures
  • a606416 test(browser): announce failed main-frame documents in observation window fixtures
  • 1100ec5 fix(browser): stop tracking main-frame documents once they finish loading
  • aceaa9c fix(browser): classify session load failures by the failure's resource type
  • ab96b05 fix(browser): judge untyped load failures only inside the observation window
  • 7395e1e fix(browser): keep an omitted request frame unknown for session load failures
  • f8eab7c fix(browser): report possible document failures without a request ID as unknown
  • 70c2de5 fix(browser): keep request ownership facts across redirect hops
  • 868b36c fix(browser): keep child-frame request ownership across redirect hops

📊 Changes

7 files changed (+732 additions, -8 deletions)

View changed files

📝 docs/browser-observation.md (+1 -1)
📝 src/browser/CdpCaptureEventHelpers.ts (+4 -0)
📝 src/browser/CdpObservationSession.ts (+108 -4)
📝 tests/boundary/browser/cdpBrowserProvider.lifecycle_failures.test.ts (+3 -0)
📝 tests/boundary/browser/cdpObservationWindowBuffer.test.ts (+26 -2)
📝 tests/boundary/browser/cdpObservationWindowScope.test.ts (+562 -1)
📝 tests/fixtures/fakeCdpBrowserEvents.ts (+28 -0)

📄 Description

Summary

Fixes #6374.

observe_web_session was supposed to record only main-frame load failures, but its filter read a frameId that Chrome's Network.loadingFailed event does not carry. As a result, every failed request in the observed tab was recorded as a session load_failed event, including images, scripts, blocked trackers and iframe documents. A failure is now recorded only when its request was announced as a main-frame Document request.

Problem and expected behavior

In src/browser/CdpObservationSession.ts, TimelineCapture.#loadingFailed skipped a failure only when params.frameId was present and named another frame. CDP's Network.loadingFailed has requestId, timestamp, type, errorText and related fields, but no frameId. So the filter never excluded anything, and a blocked ad image (net::ERR_BLOCKED_BY_CLIENT) appeared in the session timeline beside a real document failure. The public docs describe the timeline as reload, navigation, redirect, failure and lifecycle metadata for the observed page.

Expected: a load failure appears in the timeline only when it belongs to a main-frame document request, the same scope #redirect already uses for redirects.

Change and scope

  • TimelineCapture tracks the request IDs of possible main-frame Document requests from Network.requestWillBeSent: the type is absent or Document. For each, it records whether the request declared Document and the frame ID its hops named (parsed with cdpFrameId from CdpCaptureEventHelpers.ts, the same non-empty rule cdpFrameIdentity uses, so an empty ID counts as omitted), plus whether hops named different frames. The candidate also records whether that frame was the main frame when a hop first named it. Ownership (main frame, another frame, or unknown) is decided when the failure arrives: a request admitted under the main frame stays main after a root replacement, a request naming a root replacement that commits before the failure is attributed to the new main frame, and a conflict between hops stays unknown. A failure owned by another frame is filtered silently; only unknown ownership reports incomplete coverage. A redirect continues under the same request ID: a later hop that omits a fact keeps what an earlier hop established, and a hop that contradicts it makes that fact unknown. Redirect scoping is unchanged from main.
  • #loadingFailed considers a failure only for a tracked request ID, and removes the ID. The failure's own type, which CDP requires on Network.loadingFailed, decides: only Document is recorded. When the failure omits it, the request's explicit Document type is used. When neither event declares a type, the request omitted its frame, or the failure has no usable requestId (so it cannot be joined to a frame), the failure is not recorded, and if it falls inside the observation window the timeline is marked incomplete with invalid_protocol_value, so a main-frame request that omitted its optional type is not treated as proof of a document. Network.loadingFinished removes the ID of a finished document. It is handled outside the timeline and never buffered, so subresource completions do not use the pre-arm buffer. Before arming, a finished document's request may still be waiting in that buffer, so its ID stays tracked; such IDs are limited by the buffer's size. Requests are tracked even when their requestWillBeSent event was buffered before the observation window was armed, so a document requested before arming whose failure arrives inside the window is still recorded. The window logic itself is unchanged.
  • Docs: docs/browser-observation.md now says that main-frame document load failures are recorded and subresource load failures are not.
  • Tests: the fake CDP browser's session timeline now sends a failed main-frame image request alongside the failed document. Two existing observation-window tests (cdpObservationWindowBuffer, cdpObservationWindowScope) sent Network.loadingFailed with a frameId and no preceding request. They now announce the failed request as a main-frame Document first and omit the frameId, matching CDP. Their timing and window assertions are unchanged.

Follow the contribution scope.
Unresolved scope, authority, side effects, or required verification block
integration. Link the required maintainer scope decision for new providers or
capabilities involving target execution, injection, mutation, or protection
recovery; ordinary fixes within existing boundaries may state "not applicable".
Draft PRs may leave the scope decision pending and the readiness and merge checklists
unchecked. Maintainers can help resolve open questions.

Contribution scope: not applicable. This is a fix inside the existing browser observation contract.

Race-specific handling: the root-replacement cases need a cross-process root swap, which is uncommon because most Chrome navigations keep the main frame ID, while a document request is in flight. Without handling, a main-frame document failure is silently missing from the timeline. The handling added for it is one boolean per tracked request (admittedAsMain) and the classification at failure time. It was requested in review (Codex threads and baron's review); the uncommitted-replacement case was left out as a known limit because it needs pending-navigation correlation. No maintainer decision on this scope is recorded yet.

Contract and boundary impact

  • Semantic owner and earliest changed stage: TimelineCapture event admission for Network.loadingFailed.
  • Contribution scope fit and linked maintainer decision: not applicable.
  • CLI and MCP/tool-catalog contract: no schema change. observe_web_session / rea observe-web-session timelines no longer contain load_failed events for subresources or iframe documents.
  • Provider, bridge, target-format, or platform compatibility: Chrome DevTools Protocol event shape as documented. A failure whose requestWillBeSent was never observed, for example a request already in flight before the event subscription, is not recorded, because its frame cannot be established. The timeline is already reported as attach_limited from the start, which covers facts that depend on events before attachment. Known limits, each needing more than this filter can establish: a root-replacement Document request that fails before its navigation commits is treated as another frame's, because nothing at this boundary distinguishes it from a child frame without pending-navigation correlation; a request whose type is an unrecognized string counts as a non-document, since REA has no maintained ResourceType parser; and a redirect chain that changes resource type, combined with a failure that omits its required type, is reported as invalid_protocol_value rather than ignored.
  • Evidence, artifact, provenance, or reconstruction contract: fewer unrelated events in the timeline; the recorded load_failed events keep their request ID, timestamp and safe error text.
  • Target selection, authority, upstream behavior, and cleanup details (if applicable): none.
  • Generated metadata (docs/public/product-catalog.json), package, or installation impact (CI generates and validates ignored catalogs and skills/; edit authored instructions in .agents/skills/reverse-engineer-anything/ and never commit build outputs): none.

Expected effects (check known effects; use documentation-only on its own).
Unchecked items do not establish absence; leave uncertainties visible.

  • Read existing files or captured data.
  • Launch or attach to a process.
  • Inject instrumentation or run scripts/code.
  • Change process memory, registers, files, or configuration.
  • Contact network services.
  • Recover protected code or work around protections.
  • Documentation or usability only; no target interaction.
  • Not sure yet; investigation or maintainer guidance needed.

Evidence and regression coverage

  • Tests added or updated:
    • tests/boundary/browser/cdpBrowserProvider.lifecycle_failures.test.ts, "observes user-driven reload, SPA, redirect, failure, and lifecycle events", now also asserts that the timeline does not contain the subresource's ERR_BLOCKED_BY_CLIENT. The main-frame document failure is still recorded.
    • cdpObservationWindowScope, "records a load failure only for a main-frame document that has not finished": after arming, one document request finishes and then receives a stray failure, and another document request fails. Only the second is recorded. Without the loadingFinished cleanup, this test fails.
    • cdpObservationWindowScope, "uses the failure's resource type when a main-frame request omits its type": three main-frame requests without a type fail as Image, as Document, and with no type. Only the Document failure is recorded, and the timeline reports invalid_protocol_value. If an untyped request is treated as a document regardless of the failure's type, this test fails.
    • cdpObservationWindowScope, "does not report an untyped failure before the window as incomplete coverage": an untyped request and failure buffered before the window add no load_failed event and no exclusion. Applying the exclusion before the window check fails this test.
    • cdpObservationWindowScope, "reports a document failure whose request omitted its frame as unknown coverage": a Document request without a frameId and a child-frame Document request both fail. Neither is recorded. The frameless one adds exactly one invalid_protocol_value exclusion, and the child frame's adds none. Treating an omitted frame as another frame fails this test.
    • cdpObservationWindowScope, "reports a possible document failure without a request ID as unknown coverage": an Image failure and a Document failure without a requestId. Neither is recorded. Only the Document one adds a timeline invalid_protocol_value exclusion (count 1). Dropping it silently fails this test.
    • cdpObservationWindowScope, "keeps main-frame ownership across a redirect hop that omits its frame": a main-frame Document request is redirected under the same ID by a hop without a frameId, then fails. The failure is recorded. Letting the later hop overwrite the earlier facts fails this test.
    • cdpObservationWindowScope, "keeps child-frame ownership across a redirect hop that omits its frame": a child-frame Document request is redirected under the same ID by a hop without a frameId, then fails. The failure is not recorded and no exclusion is added. Not tracking the other-frame first hop fails this test.
    • cdpObservationWindowScope, "attributes a failure to a root replacement that commits after its request" and "keeps contradictory redirect ownership unknown after a later hop agrees again": the first records a Document failure whose request named the replacement frame before Page.frameNavigated committed it; the second sends hops naming the main frame, a child frame, then the main frame again, and reports the failure as unknown coverage instead of recording it. Classifying the frame when the request arrives fails the first; letting a later hop clear the conflict fails the second.
    • cdpObservationWindowScope, "keeps a former main-frame document failure after a root replacement commits": a Document request names frame-main, a root replacement commits replacement-main, then the request fails. The failure is recorded. Dropping the admission-time fact from #ownership fails this test only.
    • cdpObservationWindowScope, "treats an empty request frame ID as unknown ownership": a Document request with frameId: "" fails; nothing is recorded and the timeline reports invalid_protocol_value. Parsing the frame with cdpStringValue again fails this test.
    • cdpObservationWindowBuffer and cdpObservationWindowScope still pass with realistic events: failures before the window are dropped, failures inside it are kept, and a zero timestamp is preserved.
  • Base reproduction or other evidence: with #loadingFailed restored to main's frameId filter, the lifecycle test above fails, because the blocked image is recorded. Each new test above also fails when its fix is reverted, as noted in its entry.
  • User-visible CLI/MCP output (if applicable): subresource failures disappear from timeline.
  • Remaining proof gaps: verified only against the fake CDP browser, not against a real Chrome session.

For evidence-bearing changes:

  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

On Linux (WSL Ubuntu, Node 24.22.1) at 70d890280 (upstream/main 015414130 merged, including #4040), after npm ci and npm run build:cached:

  • npx tsc -p . --noEmit passed.
  • npx oxfmt --check on the changed files: clean. npx oxlint on the changed source and test files: 4 warnings, the same as on main.
  • npm run docs:check passed.
  • npx vitest run tests/boundary/browser src/browser: 1917 tests passed, 4 skipped, in two runs. Two tests failed in tests/boundary/browser/cdpScriptMetadata.test.ts ("keeps invalid protocol scripts out of policy filtering", "reports unsupported and invalid script exclusions separately"). They fail the same way on upstream/main 015414130 without this change; the failure sets are identical.
  • The negative checks listed under Evidence each failed their test when run during development; the fix commits are 1100ec56c through 4dac93f6a.

At 8789ea7b1 (admission-time main-frame ownership), same environment: npx tsc -p . --noEmit passed; oxfmt --check clean and oxlint 4 warnings (unchanged) on the two changed files; npx vitest run tests/boundary/browser: 1091 passed, 4 skipped, in two runs, with the same two cdpScriptMetadata failures as main. Removing admittedAsMain from #ownership failed only the new test. docs:check and src/browser were not re-run for this commit.

Compatibility, safety, and release

  • Breaking changes or migration steps: none. Consumers that counted subresource failures as session events will no longer see them.
  • Real Hopper/Ghidra, browser, or OS coverage: none; fake CDP browser only.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: fewer recorded network failures; no new data is captured.

Author readiness

Human owner: @Adolanium

  • I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work.
  • I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
  • I declare this PR ready for initial maintainer review.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/6373 **Author:** [@Adolanium](https://github.com/Adolanium) **Created:** 10/22/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `fix/observe-session-load-failures` --- ### 📝 Commits (10+) - [`dbd8aa3`](https://github.com/morluto/rea/commit/dbd8aa345a910bd8896bc23044338235c32a7f5f) fix(browser): record session load failures only for main-frame documents - [`1e48122`](https://github.com/morluto/rea/commit/1e48122ad95fa403f221463a4d396dcf4c3a2027) Merge upstream/main into fix/observe-session-load-failures - [`a606416`](https://github.com/morluto/rea/commit/a606416365112f47966e90cac4a43e45a4ddb7b7) test(browser): announce failed main-frame documents in observation window fixtures - [`1100ec5`](https://github.com/morluto/rea/commit/1100ec56cb4061ec64d5006b67574b430cb11c5b) fix(browser): stop tracking main-frame documents once they finish loading - [`aceaa9c`](https://github.com/morluto/rea/commit/aceaa9cc7d7df0e594bff71972d3aa64138d7982) fix(browser): classify session load failures by the failure's resource type - [`ab96b05`](https://github.com/morluto/rea/commit/ab96b0506d0f4dbf21eccccf2fcdfdc55ea587fe) fix(browser): judge untyped load failures only inside the observation window - [`7395e1e`](https://github.com/morluto/rea/commit/7395e1ed8c9ccd4292b6f3f618a1eb4c9697c3f1) fix(browser): keep an omitted request frame unknown for session load failures - [`f8eab7c`](https://github.com/morluto/rea/commit/f8eab7c650e85b58ebf4a28ab490590ee03d4ca6) fix(browser): report possible document failures without a request ID as unknown - [`70c2de5`](https://github.com/morluto/rea/commit/70c2de56fb0e896df1579b06121ac809c7eed6ab) fix(browser): keep request ownership facts across redirect hops - [`868b36c`](https://github.com/morluto/rea/commit/868b36c4b6a960de08608efa2946ae32a5a1c29d) fix(browser): keep child-frame request ownership across redirect hops ### 📊 Changes **7 files changed** (+732 additions, -8 deletions) <details> <summary>View changed files</summary> 📝 `docs/browser-observation.md` (+1 -1) 📝 `src/browser/CdpCaptureEventHelpers.ts` (+4 -0) 📝 `src/browser/CdpObservationSession.ts` (+108 -4) 📝 `tests/boundary/browser/cdpBrowserProvider.lifecycle_failures.test.ts` (+3 -0) 📝 `tests/boundary/browser/cdpObservationWindowBuffer.test.ts` (+26 -2) 📝 `tests/boundary/browser/cdpObservationWindowScope.test.ts` (+562 -1) 📝 `tests/fixtures/fakeCdpBrowserEvents.ts` (+28 -0) </details> ### 📄 Description ## Summary Fixes #6374. `observe_web_session` was supposed to record only main-frame load failures, but its filter read a `frameId` that Chrome's `Network.loadingFailed` event does not carry. As a result, every failed request in the observed tab was recorded as a session `load_failed` event, including images, scripts, blocked trackers and iframe documents. A failure is now recorded only when its request was announced as a main-frame Document request. ## Problem and expected behavior In `src/browser/CdpObservationSession.ts`, `TimelineCapture.#loadingFailed` skipped a failure only when `params.frameId` was present and named another frame. CDP's `Network.loadingFailed` has `requestId`, `timestamp`, `type`, `errorText` and related fields, but no `frameId`. So the filter never excluded anything, and a blocked ad image (`net::ERR_BLOCKED_BY_CLIENT`) appeared in the session timeline beside a real document failure. The public docs describe the timeline as reload, navigation, redirect, failure and lifecycle metadata for the observed page. Expected: a load failure appears in the timeline only when it belongs to a main-frame document request, the same scope `#redirect` already uses for redirects. ## Change and scope - `TimelineCapture` tracks the request IDs of possible main-frame Document requests from `Network.requestWillBeSent`: the `type` is absent or `Document`. For each, it records whether the request declared `Document` and the frame ID its hops named (parsed with `cdpFrameId` from `CdpCaptureEventHelpers.ts`, the same non-empty rule `cdpFrameIdentity` uses, so an empty ID counts as omitted), plus whether hops named different frames. The candidate also records whether that frame was the main frame when a hop first named it. Ownership (main frame, another frame, or unknown) is decided when the failure arrives: a request admitted under the main frame stays main after a root replacement, a request naming a root replacement that commits before the failure is attributed to the new main frame, and a conflict between hops stays unknown. A failure owned by another frame is filtered silently; only unknown ownership reports incomplete coverage. A redirect continues under the same request ID: a later hop that omits a fact keeps what an earlier hop established, and a hop that contradicts it makes that fact unknown. Redirect scoping is unchanged from main. - `#loadingFailed` considers a failure only for a tracked request ID, and removes the ID. The failure's own `type`, which CDP requires on `Network.loadingFailed`, decides: only `Document` is recorded. When the failure omits it, the request's explicit `Document` type is used. When neither event declares a type, the request omitted its frame, or the failure has no usable `requestId` (so it cannot be joined to a frame), the failure is not recorded, and if it falls inside the observation window the timeline is marked incomplete with `invalid_protocol_value`, so a main-frame request that omitted its optional type is not treated as proof of a document. `Network.loadingFinished` removes the ID of a finished document. It is handled outside the timeline and never buffered, so subresource completions do not use the pre-arm buffer. Before arming, a finished document's request may still be waiting in that buffer, so its ID stays tracked; such IDs are limited by the buffer's size. Requests are tracked even when their `requestWillBeSent` event was buffered before the observation window was armed, so a document requested before arming whose failure arrives inside the window is still recorded. The window logic itself is unchanged. - Docs: `docs/browser-observation.md` now says that main-frame document load failures are recorded and subresource load failures are not. - Tests: the fake CDP browser's session timeline now sends a failed main-frame image request alongside the failed document. Two existing observation-window tests (`cdpObservationWindowBuffer`, `cdpObservationWindowScope`) sent `Network.loadingFailed` with a `frameId` and no preceding request. They now announce the failed request as a main-frame Document first and omit the `frameId`, matching CDP. Their timing and window assertions are unchanged. Follow the [contribution scope](https://github.com/morluto/rea/blob/main/CONTRIBUTING.md#contribution-scope). Unresolved scope, authority, side effects, or required verification block integration. Link the required maintainer scope decision for new providers or capabilities involving target execution, injection, mutation, or protection recovery; ordinary fixes within existing boundaries may state "not applicable". Draft PRs may leave the scope decision pending and the readiness and merge checklists unchecked. Maintainers can help resolve open questions. Contribution scope: not applicable. This is a fix inside the existing browser observation contract. Race-specific handling: the root-replacement cases need a cross-process root swap, which is uncommon because most Chrome navigations keep the main frame ID, while a document request is in flight. Without handling, a main-frame document failure is silently missing from the timeline. The handling added for it is one boolean per tracked request (`admittedAsMain`) and the classification at failure time. It was requested in review (Codex threads and baron's review); the uncommitted-replacement case was left out as a known limit because it needs pending-navigation correlation. No maintainer decision on this scope is recorded yet. ## Contract and boundary impact - Semantic owner and earliest changed stage: `TimelineCapture` event admission for `Network.loadingFailed`. - Contribution scope fit and linked maintainer decision: not applicable. - CLI and MCP/tool-catalog contract: no schema change. `observe_web_session` / `rea observe-web-session` timelines no longer contain `load_failed` events for subresources or iframe documents. - Provider, bridge, target-format, or platform compatibility: Chrome DevTools Protocol event shape as documented. A failure whose `requestWillBeSent` was never observed, for example a request already in flight before the event subscription, is not recorded, because its frame cannot be established. The timeline is already reported as `attach_limited` from the start, which covers facts that depend on events before attachment. Known limits, each needing more than this filter can establish: a root-replacement Document request that fails before its navigation commits is treated as another frame's, because nothing at this boundary distinguishes it from a child frame without pending-navigation correlation; a request whose `type` is an unrecognized string counts as a non-document, since REA has no maintained `ResourceType` parser; and a redirect chain that changes resource type, combined with a failure that omits its required `type`, is reported as `invalid_protocol_value` rather than ignored. - Evidence, artifact, provenance, or reconstruction contract: fewer unrelated events in the timeline; the recorded `load_failed` events keep their request ID, timestamp and safe error text. - Target selection, authority, upstream behavior, and cleanup details (if applicable): none. - Generated metadata (`docs/public/product-catalog.json`), package, or installation impact (CI generates and validates ignored catalogs and `skills/`; edit authored instructions in `.agents/skills/reverse-engineer-anything/` and never commit build outputs): none. Expected effects (check known effects; use documentation-only on its own). Unchecked items do not establish absence; leave uncertainties visible. - [x] Read existing files or captured data. - [ ] Launch or attach to a process. - [ ] Inject instrumentation or run scripts/code. - [ ] Change process memory, registers, files, or configuration. - [ ] Contact network services. - [ ] Recover protected code or work around protections. - [ ] Documentation or usability only; no target interaction. - [ ] Not sure yet; investigation or maintainer guidance needed. ## Evidence and regression coverage - Tests added or updated: - `tests/boundary/browser/cdpBrowserProvider.lifecycle_failures.test.ts`, "observes user-driven reload, SPA, redirect, failure, and lifecycle events", now also asserts that the timeline does not contain the subresource's `ERR_BLOCKED_BY_CLIENT`. The main-frame document failure is still recorded. - `cdpObservationWindowScope`, "records a load failure only for a main-frame document that has not finished": after arming, one document request finishes and then receives a stray failure, and another document request fails. Only the second is recorded. Without the `loadingFinished` cleanup, this test fails. - `cdpObservationWindowScope`, "uses the failure's resource type when a main-frame request omits its type": three main-frame requests without a type fail as `Image`, as `Document`, and with no type. Only the `Document` failure is recorded, and the timeline reports `invalid_protocol_value`. If an untyped request is treated as a document regardless of the failure's type, this test fails. - `cdpObservationWindowScope`, "does not report an untyped failure before the window as incomplete coverage": an untyped request and failure buffered before the window add no `load_failed` event and no exclusion. Applying the exclusion before the window check fails this test. - `cdpObservationWindowScope`, "reports a document failure whose request omitted its frame as unknown coverage": a Document request without a `frameId` and a child-frame Document request both fail. Neither is recorded. The frameless one adds exactly one `invalid_protocol_value` exclusion, and the child frame's adds none. Treating an omitted frame as another frame fails this test. - `cdpObservationWindowScope`, "reports a possible document failure without a request ID as unknown coverage": an `Image` failure and a `Document` failure without a `requestId`. Neither is recorded. Only the Document one adds a `timeline` `invalid_protocol_value` exclusion (count 1). Dropping it silently fails this test. - `cdpObservationWindowScope`, "keeps main-frame ownership across a redirect hop that omits its frame": a main-frame Document request is redirected under the same ID by a hop without a `frameId`, then fails. The failure is recorded. Letting the later hop overwrite the earlier facts fails this test. - `cdpObservationWindowScope`, "keeps child-frame ownership across a redirect hop that omits its frame": a child-frame Document request is redirected under the same ID by a hop without a `frameId`, then fails. The failure is not recorded and no exclusion is added. Not tracking the other-frame first hop fails this test. - `cdpObservationWindowScope`, "attributes a failure to a root replacement that commits after its request" and "keeps contradictory redirect ownership unknown after a later hop agrees again": the first records a Document failure whose request named the replacement frame before `Page.frameNavigated` committed it; the second sends hops naming the main frame, a child frame, then the main frame again, and reports the failure as unknown coverage instead of recording it. Classifying the frame when the request arrives fails the first; letting a later hop clear the conflict fails the second. - `cdpObservationWindowScope`, "keeps a former main-frame document failure after a root replacement commits": a Document request names `frame-main`, a root replacement commits `replacement-main`, then the request fails. The failure is recorded. Dropping the admission-time fact from `#ownership` fails this test only. - `cdpObservationWindowScope`, "treats an empty request frame ID as unknown ownership": a Document request with `frameId: ""` fails; nothing is recorded and the timeline reports `invalid_protocol_value`. Parsing the frame with `cdpStringValue` again fails this test. - `cdpObservationWindowBuffer` and `cdpObservationWindowScope` still pass with realistic events: failures before the window are dropped, failures inside it are kept, and a zero timestamp is preserved. - Base reproduction or other evidence: with `#loadingFailed` restored to main's `frameId` filter, the lifecycle test above fails, because the blocked image is recorded. Each new test above also fails when its fix is reverted, as noted in its entry. - User-visible CLI/MCP output (if applicable): subresource failures disappear from `timeline`. - Remaining proof gaps: verified only against the fake CDP browser, not against a real Chrome session. For evidence-bearing changes: - [x] Observed, derived, and inferred claims remain distinguishable. - [x] Artifact identity, source provenance, and failed attempts remain preserved. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Validation performed On Linux (WSL Ubuntu, Node 24.22.1) at 70d890280 (upstream/main 015414130 merged, including #4040), after `npm ci` and `npm run build:cached`: - `npx tsc -p . --noEmit` passed. - `npx oxfmt --check` on the changed files: clean. `npx oxlint` on the changed source and test files: 4 warnings, the same as on main. - `npm run docs:check` passed. - `npx vitest run tests/boundary/browser src/browser`: 1917 tests passed, 4 skipped, in two runs. Two tests failed in `tests/boundary/browser/cdpScriptMetadata.test.ts` ("keeps invalid protocol scripts out of policy filtering", "reports unsupported and invalid script exclusions separately"). They fail the same way on upstream/main 015414130 without this change; the failure sets are identical. - The negative checks listed under Evidence each failed their test when run during development; the fix commits are 1100ec56c through 4dac93f6a. At 8789ea7b1 (admission-time main-frame ownership), same environment: `npx tsc -p . --noEmit` passed; `oxfmt --check` clean and `oxlint` 4 warnings (unchanged) on the two changed files; `npx vitest run tests/boundary/browser`: 1091 passed, 4 skipped, in two runs, with the same two `cdpScriptMetadata` failures as main. Removing `admittedAsMain` from `#ownership` failed only the new test. `docs:check` and `src/browser` were not re-run for this commit. ## Compatibility, safety, and release - Breaking changes or migration steps: none. Consumers that counted subresource failures as session events will no longer see them. - Real Hopper/Ghidra, browser, or OS coverage: none; fake CDP browser only. - Package or release metadata impact: none. - Security, privacy, process, or containment review: fewer recorded network failures; no new data is captured. ## Author readiness <!-- The human owner completes these declarations before requesting review. The PR author is the owner by default. A different owner must explicitly accept ownership on this PR. AI agents must leave these declarations to a human to dictate to them or there could be catastrophic consequences for many users of REA. Draft PRs may leave them unchecked. These declarations record initial readiness when opening the PR for review; later pushes do not require a new human sign-off. Keep the description and validation evidence current as the work changes. See CONTRIBUTING.md#pull-request-ownership-and-readiness. --> Human owner: @Adolanium - [x] I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work. - [x] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps. - [x] I declare this PR ready for initial maintainer review. ## Before merge <!-- Draft PRs may leave pending items unchecked. Resolve material scope and authority questions before integration. --> - [ ] This PR addresses a concrete problem or an agreed enhancement. - [ ] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [ ] The PR has one focused outcome and the title follows `type(scope): outcome`. - [ ] Related issue is linked, or the reason for not linking one is stated above. - [ ] Tests cover changed observable behavior and meaningful failure paths. - [ ] Owning docs, contracts, and generated metadata are updated where needed. - [ ] User-visible CLI/MCP changes include representative output. - [ ] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-11-01 21:47:00 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#7318
No description provided.