test(mv3): prove real downloads compatibility - #43
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughMV3 호환성 실행기가 ChangesMV3 호환성 검증
Estimated code review effort: 4 (Complex) | ~45 minutes Merge Risk: 🟡 Moderate · up to The PR adds real pinned-Chromium download coverage and fail-closed runner hardening, but an unhandled transport-error path could stop the compatibility run without bounded diagnostics. The required independent approval and security checks are also still outstanding, so the PR should not merge until these items are addressed or explicitly accepted. Sequence Diagram(s)sequenceDiagram
participant 호환성실행기
participant MV3서비스워커
participant LoopbackHTTP서버
participant 다운로드디렉터리
호환성실행기->>MV3서비스워커: downloads 표면 검사 요청
MV3서비스워커->>LoopbackHTTP서버: 로컬 payload 요청
LoopbackHTTP서버-->>MV3서비스워커: 다운로드 응답
MV3서비스워커->>다운로드디렉터리: 파일 저장
MV3서비스워커-->>호환성실행기: ready 상태와 진단 정보 반환
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
OpenCode cannot approve yet because required coverage evidence did not pass.
Review outcome
1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence
-
Problem: The required coverage-evidence job result was
failure, so OpenCode cannot establish approval sufficiency for this head. -
Root cause: Automated approval is only valid when the same-head coverage-evidence job proves supported repository test suites passed and configured docstring gates passed or were advisory, or reports not applicable because no supported source files or package manifests exist. Missing, failed, skipped, unavailable, or unsupported-tooling test evidence is a blocker.
-
Fix: Install or configure the repository test/docstring evidence tooling when source files or package manifests exist, rerun the current-head coverage-evidence job, and approve only after it reports
successwith required evidence or explicit no-source not-applicable evidence. -
Regression test: Keep the approval branch checking
needs.coverage-evidence.result == successbefore posting APPROVE, and publish REQUEST_CHANGES when coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence are present. -
Result: REQUEST_CHANGES
-
Reason: coverage-evidence result was
failure, so required test/docstring evidence was not proven for current head27ce89066ed1473dcd66eb26a2f91becf9df5424. -
Head SHA:
27ce89066ed1473dcd66eb26a2f91becf9df5424 -
Workflow run: 31641414172
-
Workflow attempt: 1
Coverage evidence
Coverage evidence job did not run or did not publish coverage evidence.
Changed-File Evidence Map
flowchart LR
PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
Evidence --> S1["CI script: run_mv3_compatibility.py"]
S1 --> I1["review and security gate shell path"]
I1 --> R1["Review risk: CI script: run_mv3_compatibility.py"]
R1 --> V1["bash -n plus Strix self-test"]
Evidence --> S2["Test (6 files)"]
S2 --> I2["regression suite"]
I2 --> R2["Review risk: Test (6 files)"]
R2 --> V2["targeted test run"]
OpenCode Review Overview
Pull request overviewOpenCode could not approve from deterministic current-head evidence because GitHub Checks have failed. Findings1. HIGH Current-head GitHub Checks - Fix failed required checks before approval
Failed checks:
Changed-File Evidence Mapflowchart LR
PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
Evidence --> S1["Changed file: CHANGELOG.md"]
S1 --> I1["repository behavior"]
I1 --> R1["Review risk: Changed file: CHANGELOG.md"]
R1 --> V1["required checks"]
Evidence --> S2["Docs (2 files)"]
S2 --> I2["operator or user guidance"]
I2 --> R2["Review risk: Docs (2 files)"]
R2 --> V2["docs review"]
Evidence --> S3["CI script: run_mv3_compatibility.py"]
S3 --> I3["review and security gate shell path"]
I3 --> R3["Review risk: CI script: run_mv3_compatibility.py"]
R3 --> V3["bash -n plus Strix self-test"]
Evidence --> S4["Test (13 files)"]
S4 --> I4["regression suite"]
I4 --> R4["Review risk: Test (13 files)"]
R4 --> V4["targeted test run"]
|
Governance authorization for the workflow mutation (issue #212, option 2)The repository owner authorizes the What the mutation does: Least-privilege rationale:
Boundaries preserved: this authorization covers only this exact head's three workflow lines plus the Exact-head checks must be regenerated before any integration decision. The failing Strix run predates the backend-unavailability classification fix and will be re-dispatched. |
|
Scheduled review-feedback autofix for this PR head.
|
|
Scheduled review-feedback autofix for this PR head.
|
|
Scheduled review-feedback autofix for this PR head.
|
|
Scheduled review-feedback autofix for this PR head.
|
|
Scheduled review-feedback autofix for this PR head.
|
|
Scheduled review-feedback autofix for this PR head.
|
|
Scheduled review-feedback autofix for this PR head.
|
|
Scheduled review-feedback autofix for this PR head.
|
|
Scheduled review-feedback autofix for this PR head.
|
|
Scheduled review-feedback autofix for this PR head.
|
|
Scheduled review-feedback autofix for this PR head.
|
|
Scheduled review-feedback autofix for this PR head.
|
|
Scheduled review-feedback autofix for this PR head.
|
# Conflicts: # CHANGELOG.md # docs/doctoring.md
|
Governance remediation for #212: the |
| return raw_line_bytes, oversized | ||
|
|
||
|
|
||
| def _parse_chromedriver_bound_port(raw_line_bytes: bytes, oversized: bool) -> int | None: | ||
| """Return one bounded authoritative startup port or ignore a malformed candidate.""" | ||
|
|
||
| if oversized or not raw_line_bytes.startswith( | ||
| CHROMEDRIVER_BOUND_PORT_PREFIX.encode("ascii") | ||
| ): | ||
| return None | ||
|
|
||
| raw_line = raw_line_bytes.decode("utf-8", errors="replace") | ||
| line = raw_line.rstrip("\r\n") | ||
| if not line.endswith("."): | ||
| return None | ||
| port_text = line[len(CHROMEDRIVER_BOUND_PORT_PREFIX) : -1] | ||
| if not port_text.isdecimal(): |
There was a problem hiding this comment.
🔍 ChromeDriver startup-port prefix must match the pinned build's exact stdout wording
_start_chromedriver depends on ChromeDriver emitting a line beginning with the literal CHROMEDRIVER_BOUND_PORT_PREFIX = "ChromeDriver was started successfully on port " and ending in . (run_mv3_compatibility.py, 413-429). If the pinned ChromeDriver 150.0.7871.129 phrases its readiness line differently (older builds printed the port only in the Starting ChromeDriver ... on port N line), no port is ever published and every trial fails via the 20s startup timeout. The author reports the Manifest V3 lane passed on the head commit, which implies the wording matches for this pinned build, but this string is brittle across ChromeDriver versions and worth confirming if the pin changes.
Was this helpful? React with 👍 or 👎 to provide feedback.
| transport_protocol_failed = False | ||
| try: | ||
| connection.request( | ||
| method, | ||
| path, | ||
| body=body, | ||
| headers={"Content-Type": "application/json"}, | ||
| ) | ||
| response = connection.getresponse() | ||
| raw = response.read(MAX_WEBDRIVER_RESPONSE_BYTES + 1) | ||
| try: | ||
| connection.request( | ||
| method, | ||
| path, | ||
| body=body, | ||
| headers={"Content-Type": "application/json"}, | ||
| ) | ||
| response = connection.getresponse() | ||
| raw = response.read(MAX_WEBDRIVER_RESPONSE_BYTES + 1) | ||
| except http.client.HTTPException: | ||
| transport_protocol_failed = True | ||
| if transport_protocol_failed: | ||
| raise RuntimeError("WebDriver transport protocol failure") | ||
| if len(raw) > MAX_WEBDRIVER_RESPONSE_BYTES: | ||
| raise RuntimeError("WebDriver response exceeded the bounded JSON limit") | ||
| if response.status >= 400: | ||
| detail = raw.decode("utf-8", errors="replace") | ||
| raise RuntimeError(f"WebDriver HTTP {response.status}: {detail}") | ||
| finally: | ||
| connection.close() | ||
|
|
||
| decoded = json.loads(raw.decode("utf-8")) | ||
| try: | ||
| decoded = json.loads(raw.decode("utf-8")) | ||
| except json.JSONDecodeError: | ||
| if response.status >= 400: | ||
| raise RuntimeError(f"WebDriver HTTP {response.status} error") from None | ||
| raise | ||
| if not isinstance(decoded, dict): | ||
| raise RuntimeError("WebDriver returned a non-object JSON payload") | ||
| if response.status >= 400: | ||
| value = decoded.get("value") | ||
| if isinstance(value, dict) and value.get("error"): | ||
| raise WebDriverProtocolError(value.get("error"), value.get("message")) | ||
| raise RuntimeError(f"WebDriver HTTP {response.status} error") | ||
| value = decoded.get("value") | ||
| if isinstance(value, dict) and value.get("error"): | ||
| raise RuntimeError(f"WebDriver error: {value.get('error')}: {value.get('message')}") | ||
| raise WebDriverProtocolError(value.get("error"), value.get("message")) | ||
| return decoded |
There was a problem hiding this comment.
📝 Info: Reworked transport-failure handling avoids unbound reads
When request/getresponse/read raise http.client.HTTPException, transport_protocol_failed forces a fixed RuntimeError before raw or response is used, so no unbound-variable access occurs. For status>=400 the code now decodes JSON first and raises WebDriverProtocolError only on a value.error dict, else a code-only RuntimeError. Callers catch these, so dropping raw body text is contained.
Was this helpful? React with 👍 or 👎 to provide feedback.
| return { | ||
| ready: false, | ||
| diagnostic: observedDownload ? "download-timeout" : "download-search-missing", | ||
| }; |
There was a problem hiding this comment.
📝 Info: In-progress download can be mislabeled as search-missing
waitForDownload returns download-timeout only when the item was seen via search; otherwise it returns download-search-missing. A download whose record never surfaces within 100*50ms is thus labeled the same as a genuinely absent one. Both fail closed, so this only affects diagnostic accuracy.
Was this helpful? React with 👍 or 👎 to provide feedback.
| if (item.state === "complete") { | ||
| if (item.url !== expectedUrl) { | ||
| return { ready: false, diagnostic: "download-url-mismatch" }; | ||
| } | ||
| if (item.bytesReceived !== expectedBytes || item.totalBytes !== expectedBytes) { | ||
| return { ready: false, diagnostic: "download-byte-count-mismatch" }; | ||
| } | ||
| if (item.exists === false) { | ||
| return { ready: false, diagnostic: "download-exists-false" }; | ||
| } | ||
| return { ready: true, diagnostic: "download-complete-ready" }; |
There was a problem hiding this comment.
📝 Info: Download byte-count and URL evidence match the served fixture
waitForDownload (service_worker.js) requires item.url === expectedUrl and bytesReceived === totalBytes === expectedBytes. I confirmed DOWNLOAD_PAYLOAD equals the on-disk download.txt content including the trailing newline (48 UTF-8 bytes), and the fixture server serves that file at /download.txt with a Content-Length, so totalBytes will equal the payload size. conflictAction: "uniquify" avoids restart-pass overwrite races since the second pass shares the same profile/download directory. This is a real-browser integration path whose reliability depends on new-headless download behavior, but the logic itself is consistent.
Was this helpful? React with 👍 or 👎 to provide feedback.
| cleanup_error: Exception | None = None | ||
| try: | ||
| driver.wait(timeout=5) | ||
| except subprocess.TimeoutExpired: | ||
| driver.kill() | ||
| driver.wait(timeout=5) | ||
| if session_id is not None: | ||
| try: | ||
| _json_request( | ||
| driver_port, | ||
| "DELETE", | ||
| _webdriver_path(session_id, ""), | ||
| {}, | ||
| ) | ||
| except (OSError, ValueError, RuntimeError, json.JSONDecodeError) as error: | ||
| cleanup_error = error | ||
| finally: | ||
| teardown_error = _teardown_driver_process(driver) | ||
| if primary_error is not None: | ||
| if cleanup_error is not None: | ||
| primary_error.add_note( | ||
| "WebDriver session cleanup also failed after the primary browser-pass " | ||
| f"failure: {type(cleanup_error).__name__}" | ||
| ) | ||
| if teardown_error is not None: | ||
| primary_error.add_note( | ||
| "ChromeDriver process teardown also failed after the primary browser-pass " | ||
| f"failure: {type(teardown_error).__name__}" | ||
| ) | ||
| elif cleanup_error is not None: | ||
| cleanup_failure = WebDriverSessionCleanupError( | ||
| "WebDriver session cleanup failed after bounded process teardown" | ||
| ) | ||
| if teardown_error is not None: | ||
| cleanup_failure.add_note( | ||
| "ChromeDriver process teardown also failed: " | ||
| f"{type(teardown_error).__name__}" | ||
| ) | ||
| raise cleanup_failure from cleanup_error | ||
| elif teardown_error is not None: | ||
| raise teardown_error |
There was a problem hiding this comment.
🔍 Cleanup finally can replace a primary error with an unreviewed cleanup exception
In _run_browser_pass, the session-delete only converts exceptions in (OSError, ValueError, RuntimeError, json.JSONDecodeError) into a recorded cleanup_error (run_mv3_compatibility.py). An unreviewed exception type raised by the DELETE request propagates out of the inner try, runs the finally: teardown_error = _teardown_driver_process(driver), and then escapes the outer finally. On the success path this is the intended fail-closed behavior (covered by test_unreviewed_session_cleanup_exception_is_not_silently_suppressed). But when primary_error is already set, that unreviewed cleanup exception raised from within the finally block would mask the causal primary error rather than being attached as a note. This is a narrow edge case (DELETE raising a non-listed type during an already-failing pass) and not exercised by the tests.
Was this helpful? React with 👍 or 👎 to provide feedback.
| raw_line_bytes = stream.readline(MAX_CHROMEDRIVER_STARTUP_LINE_BYTES + 1) | ||
| if not raw_line_bytes: | ||
| return b"", False | ||
|
|
||
| oversized = len(raw_line_bytes) > MAX_CHROMEDRIVER_STARTUP_LINE_BYTES | ||
| if oversized and not raw_line_bytes.endswith(b"\n"): | ||
| while True: | ||
| remainder = stream.readline(MAX_CHROMEDRIVER_STARTUP_LINE_BYTES + 1) | ||
| if not remainder or remainder.endswith(b"\n"): | ||
| break | ||
| return raw_line_bytes, oversized | ||
|
|
||
|
|
||
| def _parse_chromedriver_bound_port(raw_line_bytes: bytes, oversized: bool) -> int | None: | ||
| """Return one bounded authoritative startup port or ignore a malformed candidate.""" | ||
|
|
||
| if oversized or not raw_line_bytes.startswith( | ||
| CHROMEDRIVER_BOUND_PORT_PREFIX.encode("ascii") | ||
| ): | ||
| return None | ||
|
|
||
| raw_line = raw_line_bytes.decode("utf-8", errors="replace") | ||
| line = raw_line.rstrip("\r\n") | ||
| if not line.endswith("."): | ||
| return None | ||
| port_text = line[len(CHROMEDRIVER_BOUND_PORT_PREFIX) : -1] | ||
| if not port_text.isdecimal(): | ||
| return None | ||
| port = int(port_text) | ||
| return port if 1 <= port <= 65_535 else None | ||
|
|
||
|
|
||
| def _start_chromedriver( | ||
| chromedriver_bin: pathlib.Path, | ||
| ) -> tuple[subprocess.Popen[bytes], int]: | ||
| """Let ChromeDriver atomically bind an ephemeral port and report the bound authority. | ||
|
|
||
| The process owns port allocation by binding port zero itself. Its combined output is | ||
| continuously drained so the pipe cannot become a back-pressure failure, but only the | ||
| reviewed startup-port record is retained. Raw ChromeDriver output never enters evidence. | ||
| """ | ||
|
|
||
| driver = subprocess.Popen( | ||
| [str(chromedriver_bin), "--port=0", "--allowed-ips=127.0.0.1"], | ||
| stdout=subprocess.PIPE, | ||
| stderr=subprocess.STDOUT, | ||
| ) | ||
| if driver.stdout is None: | ||
| teardown_error = _teardown_driver_process(driver) | ||
| startup_error = RuntimeError("ChromeDriver startup output pipe was unavailable") | ||
| if teardown_error is not None: | ||
| startup_error.add_note( | ||
| "ChromeDriver process teardown also failed: " | ||
| f"{type(teardown_error).__name__}" | ||
| ) | ||
| raise startup_error | ||
|
|
||
| startup_events: queue.Queue[tuple[str, int | None]] = queue.Queue(maxsize=1) | ||
|
|
||
| def publish(event: tuple[str, int | None]) -> None: | ||
| try: | ||
| startup_events.put_nowait(event) | ||
| except queue.Full: | ||
| return | ||
|
|
||
| def drain_output() -> None: | ||
| while True: | ||
| raw_line_bytes, oversized = _read_chromedriver_startup_line(driver.stdout) | ||
| if not raw_line_bytes: | ||
| break | ||
| port = _parse_chromedriver_bound_port(raw_line_bytes, oversized) | ||
| if port is None: | ||
| continue | ||
| publish(("ready", port)) | ||
| publish(("eof", None)) |
There was a problem hiding this comment.
📝 Info: ChromeDriver port handoff via stdout drain is race-free and non-blocking
_start_chromedriver binds --port=0 and reads the bound port from a continuously-drained stdout pipe with a bounded per-line read, publishing the first valid port through a maxsize-1 queue and ignoring later events. This avoids the previous bind/release race of _free_loopback_port, prevents pipe back-pressure via the daemon drain thread, and falls back to teardown+raise on timeout/EOF. Malformed or oversized candidate records are skipped so a later valid record can still win. Logic checks out.
Was this helpful? React with 👍 or 👎 to provide feedback.
| ready = status_value.get("ready") | ||
| if ready is True: | ||
| build = status_value.get("build") | ||
| build_version = build.get("version") if isinstance(build, dict) else None | ||
| expected_prefix = f"{PINNED_CHROME_VERSION} (" | ||
| if not isinstance(build_version, str) or not ( | ||
| build_version == PINNED_CHROME_VERSION | ||
| or build_version.startswith(expected_prefix) | ||
| ): | ||
| raise RuntimeError("ChromeDriver status identity mismatch") |
There was a problem hiding this comment.
📝 Info: ChromeDriver /status build.version identity check depends on report format
_wait_for_driver (run_mv3_compatibility.py) now rejects a ready endpoint unless build.version equals 150.0.7871.129 or starts with 150.0.7871.129 (. This assumes ChromeDriver's /status build.version matches the pinned Chrome-for-Testing version string (they are versioned together in CfT) and uses the historical "<version> (<hash>)" format. If a future pinned build reports the version in a different shape (e.g. without the trailing space+paren), this would fail closed and abort every trial. Worth keeping in mind if the pinned version is bumped.
Was this helpful? React with 👍 or 👎 to provide feedback.
| "prefs": { | ||
| "download.default_directory": str(download_dir), | ||
| "download.prompt_for_download": False, | ||
| "download.directory_upgrade": True, | ||
| }, |
There was a problem hiding this comment.
📝 Info: goog:chromeOptions prefs applied to a reused profile on the restart pass
_run_browser_pass sets download.default_directory via goog:chromeOptions.prefs (run_mv3_compatibility.py) while also passing an explicit --user-data-dir. On the initial pass the temp profile is fresh so ChromeDriver writes the prefs; the restart pass reuses the same profile. This relies on the download directory pref persisting (or being re-applied) so the restart-pass download also lands in the controlled directory. If a future ChromeDriver stops applying prefs to an existing external profile, the restart-pass download could target a different directory; today it works because the directory is identical and already persisted. No bug, but a subtle dependency on ChromeDriver profile/prefs behavior.
Was this helpful? React with 👍 or 👎 to provide feedback.
| "--disable-component-update", | ||
| "--disable-sync", | ||
| "--disable-dev-shm-usage", | ||
| "--no-sandbox", | ||
| f"--user-data-dir={profile_dir}", | ||
| f"--disable-extensions-except={FIXTURE}", | ||
| f"--load-extension={FIXTURE}", | ||
| ], | ||
| "prefs": { | ||
| "download.default_directory": str(download_dir), |
There was a problem hiding this comment.
📝 Info: Headless download success depends on prefs alone without CDP setDownloadBehavior
Downloads are enabled purely via goog:chromeOptions.prefs (download.default_directory, download.prompt_for_download, download.directory_upgrade) under --headless=new (run_mv3_compatibility.py). Some headless configurations historically required Page.setDownloadBehavior over CDP for downloads to actually write to disk. For new headless plus ChromeDriver prefs this generally works, and the author reports the lane passed, so this is not flagged as a bug — but it is the single point on which the entire downloads-compatibility proof hinges.
(Refers to this code)
Was this helpful? React with 👍 or 👎 to provide feedback.
| const url = new URL("download.txt", sourceUrl).href; | ||
| let downloadId; | ||
| try { | ||
| downloadId = await chrome.downloads.download({ | ||
| url, | ||
| filename: "originweave-mv3/download.txt", | ||
| conflictAction: "uniquify", | ||
| saveAs: false, | ||
| }); | ||
| } catch (_error) { | ||
| return { ready: false, diagnostic: "download-start-rejected" }; | ||
| } | ||
| if (!Number.isInteger(downloadId)) { | ||
| return { ready: false, diagnostic: "download-start-rejected" }; | ||
| } | ||
| return waitForDownload(downloadId, url); |
There was a problem hiding this comment.
📝 Info: Restart pass relies on uniquify to avoid overwriting the first pass's download
Both browser passes in a trial share the same TemporaryDirectory profile and thus the same download_dir. The restart pass re-downloads to the same filename: "originweave-mv3/download.txt" (service_worker.js) with conflictAction: "uniquify", producing download (1).txt. Since waitForDownload verifies against the (unchanged) source URL and byte counts rather than the on-disk name, the restart pass converges correctly and does not race the first pass's file. This is intentional and covered by test_restart_pair_never_overwrites_the_previous_controlled_download, but worth noting because the correctness depends entirely on uniquify behavior in headless Chrome.
Was this helpful? React with 👍 or 👎 to provide feedback.
| sudo chown root:root .mv3-browser/chrome-linux64/chrome_sandbox | ||
| sudo chmod 4755 .mv3-browser/chrome-linux64/chrome_sandbox | ||
|
|
||
| - name: Execute real MV3 compatibility fixture | ||
| shell: bash | ||
| env: | ||
| CHROME_BIN: ${{ github.workspace }}/.mv3-browser/chrome-linux64/chrome | ||
| CHROMEDRIVER_BIN: ${{ github.workspace }}/.mv3-browser/chromedriver-linux64/chromedriver | ||
| CHROME_DEVEL_SANDBOX: ${{ github.workspace }}/.mv3-browser/chrome-linux64/chrome_sandbox |
There was a problem hiding this comment.
🔍 Workflow edit adds root-owned setuid sandbox helper
This PR modifies mv3-compatibility.yml to sudo chown root:root and sudo chmod 4755 the extracted chrome_sandbox, and adds CHROME_DEVEL_SANDBOX. CLAUDE.md states "Do not edit .github/** ... unless the human task explicitly targets governance," and AGENTS.md states scheduled agents "may not ... alter workflows." However, this workflow file is the literal subject of the MV3 compatibility lane (it is in the workflow's own path triggers and is the feature being exercised), so the edit is arguably within the reviewed feature scope rather than an out-of-scope governance change. Flagging for the reviewer to confirm whether this workflow modification is authorized under the repository's agent rules.
Was this helpful? React with 👍 or 👎 to provide feedback.
Buyer-visible gap
Partial implementation of #27. This branch proves a bounded real-Chromium Manifest V3
downloadscapability and hardens WebDriver cleanup, executable/status authority, startup/parser diagnostics, and evidence redaction without widening Agent authority.Fresh live-base state
Protected
mainis exactb05d5acca82b9d916ada2c8e82f59f92a89817e1. Current contributor head remains exacted15185a550ba28dddb05bff6a1736f9acb117e0, while the PR's recorded base snapshot is predecessor main0841d2ab3d8b5e60a03c0a8e818cf438e2716829. GitHub now reports the PR open, Ready, and non-mergeable after protected main moved.The branch changes 19 files and still includes
.github/workflows/mv3-compatibility.yml. That workflow delta configures a privileged Chrome sandbox (chown root:root, mode4755,CHROME_DEVEL_SANDBOX). Protected-mainAGENTS.mdprohibits this scheduled writer from altering workflows. The exact authority/remediation decision is routed through issue #212; this writer will not adopt, remove, rewrite, or resolve the workflow mutation without authorized governance action.Implemented boundary
The branch:
chrome.downloads.downloadplus boundedchrome.downloads.searchagainst one controlled loopback fixture;/statusidentity;Test-first lineage
Earlier exact RED/GREEN work on this branch covers primary-failure preservation, bounded ChromeDriver process recovery, executable/status identity, startup-port resilience, raw diagnostic redaction, HTTP parser classification/privacy, download-stage diagnostics, click post-condition handling, and structural changelog contracts. The temporary repository-contract probe used to expose a central coverage false-green was later reverted after the central
.githubowner repaired that control-plane defect; no foreign workflow implementation was duplicated locally.Historical exact-head evidence only
On exact head
ed15185a550ba28dddb05bff6a1736f9acb117e0against predecessor-base lineage:32454994923: success;32454994877: success;32454994850: success; and32454994896: success.Those results remain useful branch-history evidence only. They are not promoted as current-live-base merge evidence after protected main advanced. No queued, skipped, cancelled, predecessor-head, synthetic, status-only, or model-only result is represented as current proof.
Review / convergence state
Historical OpenCode
CHANGES_REQUESTEDreviews remain predecessor-head evidence and do not transfer. No qualifying independent current-head/latest-pushAPPROVEDreview is established by current inventory, and automated comments/statuses do not manufacture approval.Before any integration decision, an authorized/suitable reconstruction path must inspect the intervening protected-main delta, preserve the unique product/test work, deliberately reconcile this branch against the then-current live base without force-push/destructive rebase or predecessor-evidence transfer, and disposition the workflow mutation through issue #212. Then regenerate all applicable exact-head CI, real MV3/browser, coverage, SAST, Security Scan, and review/approval evidence.
Scope boundary
This PR proves one controlled Manifest V3 downloads capability plus fail-closed cleanup, causal-error preservation, bounded process recovery/startup output, executable/status authority, and diagnostic behavior. It does not prove full extension compatibility, extension-to-Agent authority isolation, native messaging, enterprise policy, Chrome Web Store behavior, Google services, codecs/DRM, remote-download policy, credentials, arbitrary filesystem authority, production browser-adapter authenticity, or release readiness. Those remain governed by #27/#28 and protected-main truth.
Governance
This scheduled actor does not merge, self-approve, tag, publish, alter workflows, add secrets, weaken checks, force-push, destructively rebase, or synthesize approval.