[PR #5933] fix(browser): resolve container-less frames in attached service-worker checks #7107

Open
opened 2026-11-01 21:45:26 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/5933
Author: @omalleyandy
Created: 10/21/2026
Status: 🔄 Open

Base: main ← Head: claude/goofy-moore-8b3b6a


📝 Commits (7)

  • 3090e6f fix(browser): resolve container-less frames in attached service-worker checks
  • ccc67a2 Merge remote-tracking branch 'origin/main' into claude/goofy-moore-8b3b6a
  • 29be178 Merge remote-tracking branch 'origin/main' into claude/goofy-moore-8b3b6a
  • 990321f test(browser): cover nested container-less frames in the service-worker walk
  • a0e7de0 fix(browser): require a shared partition before using a parent's registrations
  • ae367a4 Merge remote-tracking branch 'origin/main' into claude/goofy-moore-8b3b6a
  • 23114d4 Merge remote-tracking branch 'origin/main' into claude/goofy-moore-8b3b6a

📊 Changes

8 files changed (+438 additions, -6 deletions)

View changed files

📝 docs/browser-scenario-contract.md (+10 -1)
📝 src/browser/PlaywrightScenarioServiceWorkers.ts (+40 -5)
📝 src/browser/PlaywrightScenarioSession.test.ts (+32 -0)
📝 src/domain/analysisErrorPresentation.ts (+6 -0)
📝 src/domain/analysisErrorProjection.ts (+1 -0)
📝 src/domain/browserObservationError.ts (+2 -0)
📝 src/domain/browserObservationErrors.ts (+1 -0)
➕ tests/acceptance/browser/browserScenarioServiceWorkerFramesCli.test.ts (+346 -0)

📄 Description

Summary

capture_browser_scenario in connect mode, with the default environment.service_workers: "block", failed with a generic execution_failure on pages that contain about:blank, blob:, or sandboxed iframes. These are common on pages with third-party ad and consent frames. The error carried only details: {provider_id, operation}, so the cause was not visible through the CLI or MCP, even with REA_LOG_LEVEL=debug.

After this change, such pages capture normally. A frame whose service-worker state genuinely cannot be determined now fails with the specific reason service_worker_state_unknown, a diagnostic that names the case, and the allow remediation.

No issue is linked; this was found while using the tool, and the local fixture in the new test is the public reproduction.

Problem and expected behavior

Smallest trigger: attach to any page that has an <iframe></iframe>, <iframe src="about:blank">, a blob: iframe, or <iframe sandbox> (without allow-same-origin), then run any connect-mode scenario with the default policy.

PlaywrightScenarioServiceWorkers.assertBlocked calls navigator.serviceWorker.getRegistrations() in every frame and rethrows any rejection from a frame that is still attached. Observed in Chrome 155.0.8059.85:

Frame kind Observed result
about:blank (initial, explicit, document.write), blob: getRegistrations() rejects with InvalidStateError, because Chromium creates no service-worker container for these documents. controller is null, and fetch() from an about:blank child of a controlled page goes to the network.
sandboxed without allow-same-origin Reading navigator.serviceWorker throws SecurityError ("Service worker is disabled because the context is sandboxed…").
srcdoc Inherits the parent's controller and registrations; answers normally.
data:, top-level about:blank Insecure context with an opaque origin; serviceWorker is not exposed, which was already handled.

PlaywrightBrowserScenarioProvider then wrapped the raw Playwright error in ProviderAdapterError, whose projection deliberately omits the cause.

The contract invariant is that block refuses pages whose frames are controlled or registered, and must not treat unresolved state as absent.

Change and scope

The policy owner now classifies frames by observed Chromium behaviour. It handles its own failures with tagged errors instead of passing raw errors through:

  • Opaque-origin frames cannot be controlled and cannot hold a registration, so they are not applicable. This is checked before reading the container, which Chromium rejects in sandboxed documents.
  • Container-less frames (about:blank, blob:) cannot be controlled. Their registrations belong to the storage key they share with a contiguous same-origin parent, so that parent's container is queried instead. The parent must also have the same credentialless state, because a credentialless frame keeps a nonced storage key while reporting its parent's origin (Codex review finding). The walk stops at the first ancestor that fails either check, because the storage key can differ beyond it.
  • Unknown state. In two cases the capture fails with BrowserObservationError reason service_worker_state_unknown (code target_unavailable, category unavailable), where it used to fail with a generic execution_failure. Either a container-less frame has no such parent, for example an about:blank whose initiator origin differs from its parent's or a credentialless frame under a non-credentialless parent, or the check fails in a live frame. details.diagnostics.frame_state is no_same_partition_container or check_failed, so callers can tell a structural limit from a failed check. The raw cause stays in the error chain and is not projected, so no target content leaks.
  • Precedence. A definite refusal (service_worker_blocked, target_not_allowed) in any frame still outranks an unknown frame.
  • Closed pages. Behaviour is unchanged: a rejection while the page is closed is rethrown as before.

Intentionally not included:

  • ProviderAdapterError still does not project raw causes, because raw Playwright messages can contain target content.
  • Other unclassified raw failures in this provider, such as those in cleanup paths, are unchanged.
  • The check still runs in the page's main world, so page code could spoof it. That was already true before this change.

Contribution scope: an ordinary fix within an existing boundary, so a maintainer scope decision is not applicable.

Contract and boundary impact

  • Semantic owner and earliest changed stage: PlaywrightScenarioServiceWorkers.assertBlocked (the attached service-worker policy owner); the failure-reason vocabulary in src/domain/browserObservationErrors.ts.
  • Contribution scope fit and linked maintainer decision: an ordinary fix within an existing boundary; not applicable.
  • CLI and MCP/tool-catalog contract: no schema change. Adds the failure reason service_worker_state_unknown, with its code, category, message, remediation and details.diagnostics.frame_state. The shared error projection serves both adapters; only the CLI was exercised end-to-end.
  • Provider, bridge, target-format, or platform compatibility: Chromium-specific behaviour, observed on Chrome 155.0.8059.85 / Windows 11 only.
  • Evidence, artifact, provenance, or reconstruction contract: none changed; failures still retain partial_observation.
  • Target selection, authority, upstream behavior, and cleanup details: the in-page check may now read getRegistrations() from a same-origin parent window. It is read-only and does not change cleanup or restoration.
  • Generated metadata, package, or installation impact: none. docs:check, including the error-schema check, passes unchanged.

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. (Existing connect-mode attachment, unchanged.)
  • Inject instrumentation or run scripts/code. (The existing in-page policy check is modified, and stays read-only.)
  • 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/acceptance/browser/browserScenarioServiceWorkerFramesCli.test.ts drives the real CLI (scripts/rea.mjs capture-browser-scenario) in connect mode against headless Chrome started with --remote-debugging-port=0 and a local two-origin fixture. It skips when no Chrome is found, using the same discovery as cdpSvgXlinkHref.test.ts.
      • Fixture with about:blank (×3 variants), blob:, srcdoc, data:, sandboxed (×2), an about:blank nested in a document-written about:blank, and an about:blank nested in a cross-origin frame: the capture succeeds.
      • An about:blank child of a cross-origin frame, navigated by the top document so its origin differs from its parent's: the capture fails with service_worker_state_unknown and frame_state: no_same_partition_container.
      • An attached page whose credentialless iframe registered a worker before REA attached and then navigated itself to about:blank: the capture fails with frame_state: no_same_partition_container. The same-origin parent sees no registrations.
    • src/browser/PlaywrightScenarioSession.test.ts gains two module tests for a path real Chrome cannot trigger on cue: the check rejecting in a live frame yields check_failed with the cause retained, and a later frame's service_worker_blocked outranks it.
  • Base reproduction (executed): on the base revision both acceptance tests fail. The capture returns
    "code":"execution_failure", "details":{"provider_id":"rea-playwright-browser-scenario","operation":"capture_browser_scenario","partial_observation":{…}}
    with the limitation "Scenario navigation failed before the initial state was captured." The two module tests also fail against the base PlaywrightScenarioServiceWorkers.ts.

  • User-visible CLI output after the fix, for the unknown case (abridged):

    {
      "error": "Browser scenario capture failed",
      "code": "target_unavailable",
      "category": "unavailable",
      "message": "capture_browser_scenario refused the selected page because it could not determine whether one of its frames is controlled by or has a registered service worker while the service-worker policy is block. Set environment.service_workers to allow to permit observation.",
      "remediation": { "action": "Set environment.service_workers to allow to permit this capture. REA does not treat a frame whose service-worker state it cannot read as unregistered." },
      "details": {
        "operation": "capture_browser_scenario",
        "reason": "service_worker_state_unknown",
        "diagnostics": { "frame_state": "no_same_partition_container" },
        "partial_observation": { "kind": "browser-scenario-observation", "…": "…" }
      }
    }
    
  • Remaining proof gaps: see the Validation and Compatibility sections.

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

All runs were on Windows 11 with Chrome 155.0.8059.85, Node 24.18.0 and playwright-core 1.58.2.

  • npm run test:focused -- tests/acceptance/browser/browserScenarioServiceWorkerFramesCli.test.ts (with REA_BROWSER_EXECUTABLE pointing at Chrome): 2 failed on the base revision, and 2 passed after the fix. It passed 3 further repeated runs.
  • npm run test:local -- src/browser/PlaywrightScenarioSession.test.ts with the base PlaywrightScenarioServiceWorkers.ts restored: the 2 new tests failed. With the fix: 38 passed.
  • npm run test:focused -- tests/acceptance/browser/browserScenarioServiceWorkerFramesCli.test.ts src/browser/PlaywrightScenarioSession.test.ts src/domain/analysisErrorProjection.test.ts src/browser/BrowserScenario.test.ts: 116 passed.
  • npm run check:fast: passed.
  • npm run docs:check: passed.
  • After the latest merge of main (29be178cb):
    • The same four files plus tests/boundary/browser/cdpTargetDeadlines.test.ts, through npm run test:focused with real Chrome: 138 passed.
    • npm run check:fast: passed.
    • docs:check was not re-run after the merges.
  • Review follow-up (990321fde, Greptile finding on nested container-less frames): the fixture now nests an about:blank inside a document-written about:blank.
    • Run against real Chrome, the success case fails when the walk is mutated to test the current window's DOMException (frame_state: check_failed) or to stop after one parent (frame_state: no_same_origin_container, since renamed). Before this change, the first mutation would have passed.
    • With the code restored, the same five-file npm run test:focused run (138 tests) and npm run check:fast pass.
  • Review follow-up (a0e7de0d2, Codex finding on storage partitions):
    • Probed in Chrome 155 over CDP. A credentialless iframe registered a worker under a nonced key (Storage.getStorageKeyForFrame). Its same-origin parent saw 0 registrations. After the iframe navigated to about:blank it kept the nonced key, and reported credentialless: true and the parent's origin.
    • The new acceptance case for that page captured successfully before the fix and is refused after it.
    • npm run test:focused on the same five files: 139 passed. npm run check:fast and npm run docs:check pass. Two repeat runs of the browser test pass.
  • After the latest merge of main (ae367a409):
    • npm run test:focused with real Chrome passed for the acceptance test, BrowserScenario.test.ts, analysisErrorProjection.test.ts, cdpTargetDeadlines.test.ts and cdpFailedAdmission.test.ts. npm run check:fast and npm run docs:check pass.
    • In src/browser/PlaywrightScenarioSession.test.ts, 14 upstream tests fail locally; this PR's two tests there pass. The same 14 fail with upstream's unmodified PlaywrightScenarioServiceWorkers.ts and with upstream's unmodified test file. One visible cause is that upstream's storage mock lacks the limitations() method that PlaywrightScenarioSession.ts calls. Upstream CI for that commit was still running, so whether CI sees the same failures is unverified.
    • These local runs used Node 24.18.0; main now pins 24.22.1.
  • After merge 23114d4f9, the same five files through npm run test:focused with real Chrome: 128 passed. npm run check:fast passes. The same 14 upstream PlaywrightScenarioSession.test.ts failures remain.
  • npm run test:local -- tests/evaluation/testDiscovery.test.ts tests/evaluation/ciTestSelection.test.ts: 25 passed.
  • Real-site connect-mode run, using the CLI built from this branch. The target was a production site with third-party ad and consent iframes, in an existing debug Chrome attached over CDP. It used the default service_workers: "block", a 10 s settle step, and network capture with metadata only.
    • Before the fix, the same scenario had failed in about 0.5 s with execution_failure.
    • After the fix it succeeded: both steps completed, with 85 network transactions (each with request, response and finished events), 0 events dropped and empty events.omissions.
    • completeness reports incomplete with missing_sections: ["events"]. Connect mode always reports this, because events from before attaching cannot be recovered; it is not data loss.

Compatibility, safety, and release

  • Breaking changes or migration steps: none. A new failure reason replaces a generic execution_failure for cases that were previously failing anyway. Pages that previously failed only because of container-less or opaque frames now succeed.
  • Real Hopper/Ghidra, browser, or OS coverage:
    • Only Windows 11 with Chrome 155.0.8059.85 was exercised.
    • Linux and macOS, other Chrome or Chromium versions, and the npm run verify:browser lane (including its service-worker checks) were not run, nor was npm run check:pr.
    • The MCP adapter was not exercised end-to-end with a real browser; it shares the error projection, but that is source-derived, not observed.
    • On Windows and macOS, the new acceptance test runs only when REA_BROWSER_EXECUTABLE is set.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: the projected error contains only REA-defined vocabulary. Raw browser and page error messages, frame URLs and origins are not projected. As before, the check trusts page-realm functions.

main was merged into this branch four times to resolve conflicts, with no rebase:

  • with #6036 (merge ccc67a215), whose fixed 10-second deadline now wraps the first assertBlocked call;
  • with b2fad362f (merge 29be178cb), which dropped the AnalysisCancelledError import;
  • with 0729aba6d (merge ae367a409), whose contract paragraph about policy checks bracketing artifact capture now follows this change's frame paragraph in docs/browser-scenario-contract.md. Its collected_artifacts diagnostics merge with frame_state through retainDiagnostics;
  • with 52aeeee76 (merge 23114d4f9). The previous merge commit's pre-commit formatter had rewrapped six unrelated upstream files, and one of them then conflicted. This merge takes upstream's exact versions, so the PR diff again contains only this change's 8 files.

The classification in this change is unchanged throughout.

Remaining cleanup: other capture_browser_scenario provider failures can still project as a bare execution_failure with only provider_id and operation in details, and their underlying cause is not surfaced, even at REA_LOG_LEVEL=debug. This PR names the cause only for the service-worker frame check and leaves the general error projection unchanged.

Author readiness

Human owner: @omalleyandy

  • 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.

🤖 Generated with Claude Code


🔄 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/5933 **Author:** [@omalleyandy](https://github.com/omalleyandy) **Created:** 10/21/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `claude/goofy-moore-8b3b6a` --- ### 📝 Commits (7) - [`3090e6f`](https://github.com/morluto/rea/commit/3090e6f90cbdcf30a8595b75cbc0daa087e7418c) fix(browser): resolve container-less frames in attached service-worker checks - [`ccc67a2`](https://github.com/morluto/rea/commit/ccc67a21564c408562574df02804762a49db38a9) Merge remote-tracking branch 'origin/main' into claude/goofy-moore-8b3b6a - [`29be178`](https://github.com/morluto/rea/commit/29be178cb261606e61feb593bb992ce8ddfb7ada) Merge remote-tracking branch 'origin/main' into claude/goofy-moore-8b3b6a - [`990321f`](https://github.com/morluto/rea/commit/990321fde02c1cd03342d9568f87258587981712) test(browser): cover nested container-less frames in the service-worker walk - [`a0e7de0`](https://github.com/morluto/rea/commit/a0e7de0d205ed9585236e891a902bd863ef535b7) fix(browser): require a shared partition before using a parent's registrations - [`ae367a4`](https://github.com/morluto/rea/commit/ae367a409d24e5839100737948ab78c48af074f7) Merge remote-tracking branch 'origin/main' into claude/goofy-moore-8b3b6a - [`23114d4`](https://github.com/morluto/rea/commit/23114d4f90a407f2cf6f0d7deb285b9421c54748) Merge remote-tracking branch 'origin/main' into claude/goofy-moore-8b3b6a ### 📊 Changes **8 files changed** (+438 additions, -6 deletions) <details> <summary>View changed files</summary> 📝 `docs/browser-scenario-contract.md` (+10 -1) 📝 `src/browser/PlaywrightScenarioServiceWorkers.ts` (+40 -5) 📝 `src/browser/PlaywrightScenarioSession.test.ts` (+32 -0) 📝 `src/domain/analysisErrorPresentation.ts` (+6 -0) 📝 `src/domain/analysisErrorProjection.ts` (+1 -0) 📝 `src/domain/browserObservationError.ts` (+2 -0) 📝 `src/domain/browserObservationErrors.ts` (+1 -0) ➕ `tests/acceptance/browser/browserScenarioServiceWorkerFramesCli.test.ts` (+346 -0) </details> ### 📄 Description ## Summary `capture_browser_scenario` in connect mode, with the default `environment.service_workers: "block"`, failed with a generic `execution_failure` on pages that contain about:blank, blob:, or sandboxed iframes. These are common on pages with third-party ad and consent frames. The error carried only `details: {provider_id, operation}`, so the cause was not visible through the CLI or MCP, even with `REA_LOG_LEVEL=debug`. After this change, such pages capture normally. A frame whose service-worker state genuinely cannot be determined now fails with the specific reason `service_worker_state_unknown`, a diagnostic that names the case, and the `allow` remediation. No issue is linked; this was found while using the tool, and the local fixture in the new test is the public reproduction. ## Problem and expected behavior Smallest trigger: attach to any page that has an `<iframe></iframe>`, `<iframe src="about:blank">`, a `blob:` iframe, or `<iframe sandbox>` (without `allow-same-origin`), then run any connect-mode scenario with the default policy. `PlaywrightScenarioServiceWorkers.assertBlocked` calls `navigator.serviceWorker.getRegistrations()` in every frame and rethrows any rejection from a frame that is still attached. Observed in Chrome 155.0.8059.85: | Frame kind | Observed result | | --- | --- | | about:blank (initial, explicit, `document.write`), blob: | `getRegistrations()` rejects with `InvalidStateError`, because Chromium creates no service-worker container for these documents. `controller` is `null`, and `fetch()` from an about:blank child of a controlled page goes to the network. | | sandboxed without `allow-same-origin` | Reading `navigator.serviceWorker` throws `SecurityError` ("Service worker is disabled because the context is sandboxed…"). | | srcdoc | Inherits the parent's controller and registrations; answers normally. | | data:, top-level about:blank | Insecure context with an opaque origin; `serviceWorker` is not exposed, which was already handled. | `PlaywrightBrowserScenarioProvider` then wrapped the raw Playwright error in `ProviderAdapterError`, whose projection deliberately omits the cause. The contract invariant is that `block` refuses pages whose frames are controlled or registered, and must not treat unresolved state as absent. ## Change and scope The policy owner now classifies frames by observed Chromium behaviour. It handles its own failures with tagged errors instead of passing raw errors through: - **Opaque-origin frames** cannot be controlled and cannot hold a registration, so they are not applicable. This is checked before reading the container, which Chromium rejects in sandboxed documents. - **Container-less frames** (about:blank, blob:) cannot be controlled. Their registrations belong to the storage key they share with a contiguous same-origin parent, so that parent's container is queried instead. The parent must also have the same `credentialless` state, because a credentialless frame keeps a nonced storage key while reporting its parent's origin (Codex review finding). The walk stops at the first ancestor that fails either check, because the storage key can differ beyond it. - **Unknown state.** In two cases the capture fails with `BrowserObservationError` reason `service_worker_state_unknown` (code `target_unavailable`, category `unavailable`), where it used to fail with a generic `execution_failure`. Either a container-less frame has no such parent, for example an about:blank whose initiator origin differs from its parent's or a credentialless frame under a non-credentialless parent, or the check fails in a live frame. `details.diagnostics.frame_state` is `no_same_partition_container` or `check_failed`, so callers can tell a structural limit from a failed check. The raw cause stays in the error chain and is not projected, so no target content leaks. - **Precedence.** A definite refusal (`service_worker_blocked`, `target_not_allowed`) in any frame still outranks an unknown frame. - **Closed pages.** Behaviour is unchanged: a rejection while the page is closed is rethrown as before. Intentionally not included: - `ProviderAdapterError` still does not project raw causes, because raw Playwright messages can contain target content. - Other unclassified raw failures in this provider, such as those in cleanup paths, are unchanged. - The check still runs in the page's main world, so page code could spoof it. That was already true before this change. Contribution scope: an ordinary fix within an existing boundary, so a maintainer scope decision is not applicable. ## Contract and boundary impact - Semantic owner and earliest changed stage: `PlaywrightScenarioServiceWorkers.assertBlocked` (the attached service-worker policy owner); the failure-reason vocabulary in `src/domain/browserObservationErrors.ts`. - Contribution scope fit and linked maintainer decision: an ordinary fix within an existing boundary; not applicable. - CLI and MCP/tool-catalog contract: no schema change. Adds the failure reason `service_worker_state_unknown`, with its code, category, message, remediation and `details.diagnostics.frame_state`. The shared error projection serves both adapters; only the CLI was exercised end-to-end. - Provider, bridge, target-format, or platform compatibility: Chromium-specific behaviour, observed on Chrome 155.0.8059.85 / Windows 11 only. - Evidence, artifact, provenance, or reconstruction contract: none changed; failures still retain `partial_observation`. - Target selection, authority, upstream behavior, and cleanup details: the in-page check may now read `getRegistrations()` from a same-origin parent window. It is read-only and does not change cleanup or restoration. - Generated metadata, package, or installation impact: none. `docs:check`, including the error-schema check, passes unchanged. 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. - [x] Launch or attach to a process. (Existing connect-mode attachment, unchanged.) - [x] Inject instrumentation or run scripts/code. (The existing in-page policy check is modified, and stays read-only.) - [ ] 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/acceptance/browser/browserScenarioServiceWorkerFramesCli.test.ts` drives the real CLI (`scripts/rea.mjs capture-browser-scenario`) in connect mode against headless Chrome started with `--remote-debugging-port=0` and a local two-origin fixture. It skips when no Chrome is found, using the same discovery as `cdpSvgXlinkHref.test.ts`. - Fixture with about:blank (×3 variants), blob:, srcdoc, data:, sandboxed (×2), an about:blank nested in a document-written about:blank, and an about:blank nested in a cross-origin frame: the capture succeeds. - An about:blank child of a cross-origin frame, navigated by the top document so its origin differs from its parent's: the capture fails with `service_worker_state_unknown` and `frame_state: no_same_partition_container`. - An attached page whose credentialless iframe registered a worker before REA attached and then navigated itself to about:blank: the capture fails with `frame_state: no_same_partition_container`. The same-origin parent sees no registrations. - `src/browser/PlaywrightScenarioSession.test.ts` gains two module tests for a path real Chrome cannot trigger on cue: the check rejecting in a live frame yields `check_failed` with the cause retained, and a later frame's `service_worker_blocked` outranks it. - Base reproduction (executed): on the base revision both acceptance tests fail. The capture returns `"code":"execution_failure"`, `"details":{"provider_id":"rea-playwright-browser-scenario","operation":"capture_browser_scenario","partial_observation":{…}}` with the limitation "Scenario navigation failed before the initial state was captured." The two module tests also fail against the base `PlaywrightScenarioServiceWorkers.ts`. - User-visible CLI output after the fix, for the unknown case (abridged): ```json { "error": "Browser scenario capture failed", "code": "target_unavailable", "category": "unavailable", "message": "capture_browser_scenario refused the selected page because it could not determine whether one of its frames is controlled by or has a registered service worker while the service-worker policy is block. Set environment.service_workers to allow to permit observation.", "remediation": { "action": "Set environment.service_workers to allow to permit this capture. REA does not treat a frame whose service-worker state it cannot read as unregistered." }, "details": { "operation": "capture_browser_scenario", "reason": "service_worker_state_unknown", "diagnostics": { "frame_state": "no_same_partition_container" }, "partial_observation": { "kind": "browser-scenario-observation", "…": "…" } } } ``` - Remaining proof gaps: see the Validation and Compatibility sections. 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 All runs were on Windows 11 with Chrome 155.0.8059.85, Node 24.18.0 and playwright-core 1.58.2. - `npm run test:focused -- tests/acceptance/browser/browserScenarioServiceWorkerFramesCli.test.ts` (with `REA_BROWSER_EXECUTABLE` pointing at Chrome): 2 failed on the base revision, and 2 passed after the fix. It passed 3 further repeated runs. - `npm run test:local -- src/browser/PlaywrightScenarioSession.test.ts` with the base `PlaywrightScenarioServiceWorkers.ts` restored: the 2 new tests failed. With the fix: 38 passed. - `npm run test:focused -- tests/acceptance/browser/browserScenarioServiceWorkerFramesCli.test.ts src/browser/PlaywrightScenarioSession.test.ts src/domain/analysisErrorProjection.test.ts src/browser/BrowserScenario.test.ts`: 116 passed. - `npm run check:fast`: passed. - `npm run docs:check`: passed. - After the latest merge of `main` (`29be178cb`): - The same four files plus `tests/boundary/browser/cdpTargetDeadlines.test.ts`, through `npm run test:focused` with real Chrome: 138 passed. - `npm run check:fast`: passed. - `docs:check` was not re-run after the merges. - Review follow-up (`990321fde`, Greptile finding on nested container-less frames): the fixture now nests an about:blank inside a document-written about:blank. - Run against real Chrome, the success case fails when the walk is mutated to test the current window's `DOMException` (`frame_state: check_failed`) or to stop after one parent (`frame_state: no_same_origin_container`, since renamed). Before this change, the first mutation would have passed. - With the code restored, the same five-file `npm run test:focused` run (138 tests) and `npm run check:fast` pass. - Review follow-up (`a0e7de0d2`, Codex finding on storage partitions): - Probed in Chrome 155 over CDP. A credentialless iframe registered a worker under a nonced key (`Storage.getStorageKeyForFrame`). Its same-origin parent saw 0 registrations. After the iframe navigated to about:blank it kept the nonced key, and reported `credentialless: true` and the parent's origin. - The new acceptance case for that page captured successfully before the fix and is refused after it. - `npm run test:focused` on the same five files: 139 passed. `npm run check:fast` and `npm run docs:check` pass. Two repeat runs of the browser test pass. - After the latest merge of `main` (`ae367a409`): - `npm run test:focused` with real Chrome passed for the acceptance test, `BrowserScenario.test.ts`, `analysisErrorProjection.test.ts`, `cdpTargetDeadlines.test.ts` and `cdpFailedAdmission.test.ts`. `npm run check:fast` and `npm run docs:check` pass. - In `src/browser/PlaywrightScenarioSession.test.ts`, 14 upstream tests fail locally; this PR's two tests there pass. The same 14 fail with upstream's unmodified `PlaywrightScenarioServiceWorkers.ts` and with upstream's unmodified test file. One visible cause is that upstream's storage mock lacks the `limitations()` method that `PlaywrightScenarioSession.ts` calls. Upstream CI for that commit was still running, so whether CI sees the same failures is unverified. - These local runs used Node 24.18.0; `main` now pins 24.22.1. - After merge `23114d4f9`, the same five files through `npm run test:focused` with real Chrome: 128 passed. `npm run check:fast` passes. The same 14 upstream `PlaywrightScenarioSession.test.ts` failures remain. - `npm run test:local -- tests/evaluation/testDiscovery.test.ts tests/evaluation/ciTestSelection.test.ts`: 25 passed. - Real-site connect-mode run, using the CLI built from this branch. The target was a production site with third-party ad and consent iframes, in an existing debug Chrome attached over CDP. It used the default `service_workers: "block"`, a 10 s settle step, and network capture with metadata only. - Before the fix, the same scenario had failed in about 0.5 s with `execution_failure`. - After the fix it succeeded: both steps completed, with 85 network transactions (each with request, response and finished events), 0 events dropped and empty `events.omissions`. - `completeness` reports `incomplete` with `missing_sections: ["events"]`. Connect mode always reports this, because events from before attaching cannot be recovered; it is not data loss. ## Compatibility, safety, and release - Breaking changes or migration steps: none. A new failure reason replaces a generic `execution_failure` for cases that were previously failing anyway. Pages that previously failed only because of container-less or opaque frames now succeed. - Real Hopper/Ghidra, browser, or OS coverage: - Only Windows 11 with Chrome 155.0.8059.85 was exercised. - Linux and macOS, other Chrome or Chromium versions, and the `npm run verify:browser` lane (including its service-worker checks) were not run, nor was `npm run check:pr`. - The MCP adapter was not exercised end-to-end with a real browser; it shares the error projection, but that is source-derived, not observed. - On Windows and macOS, the new acceptance test runs only when `REA_BROWSER_EXECUTABLE` is set. - Package or release metadata impact: none. - Security, privacy, process, or containment review: the projected error contains only REA-defined vocabulary. Raw browser and page error messages, frame URLs and origins are not projected. As before, the check trusts page-realm functions. `main` was merged into this branch four times to resolve conflicts, with no rebase: - with #6036 (merge `ccc67a215`), whose fixed 10-second deadline now wraps the first `assertBlocked` call; - with `b2fad362f` (merge `29be178cb`), which dropped the `AnalysisCancelledError` import; - with `0729aba6d` (merge `ae367a409`), whose contract paragraph about policy checks bracketing artifact capture now follows this change's frame paragraph in `docs/browser-scenario-contract.md`. Its `collected_artifacts` diagnostics merge with `frame_state` through `retainDiagnostics`; - with `52aeeee76` (merge `23114d4f9`). The previous merge commit's pre-commit formatter had rewrapped six unrelated upstream files, and one of them then conflicted. This merge takes upstream's exact versions, so the PR diff again contains only this change's 8 files. The classification in this change is unchanged throughout. Remaining cleanup: other `capture_browser_scenario` provider failures can still project as a bare `execution_failure` with only `provider_id` and `operation` in `details`, and their underlying cause is not surfaced, even at `REA_LOG_LEVEL=debug`. This PR names the cause only for the service-worker frame check and leaves the general error projection unchanged. ## 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: @omalleyandy - [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. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
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#7107
No description provided.