feat(bin): add fm-firefox isolated Firefox/Marionette session owner - #2025
Closed
AbdullahPesteli wants to merge 2 commits into
Closed
AbdullahPesteli wants to merge 2 commits into
AbdullahPesteli wants to merge 2 commits into
Conversation
Productize the proven web-ext + Marionette route as a harness-agnostic CLI that owns exactly one isolated, disposable Firefox: a fresh task-owned profile, a self-allocated loopback Marionette endpoint that refuses the shared 2828, a single -no-remote launch, temporary add-on install over the supported Mozilla route, truthful machine-readable receipts, and cleanup that tears down only what it created and refuses ambiguous PID/port/profile ownership. Cookie scope stays truthful: the tool isolates only its own Firefox profile and states that a project native host can still discover unrelated Gecko cookie profiles unless the project supplies its own verified cookie-off contract, forwarded through a generic env/cookie-off hook with no project business logic baked in. Add the agent-only firefox-marionette skill and its AGENTS.md section 13 trigger, operator and verification docs, a hermetic command-surface test driving a fake Firefox that speaks the real Marionette wire format, and an opt-in live proof that self-skips when Firefox, python3, or a WebDriver client are absent so standard CI stays hermetic.
…haned process/profile
Owner
|
Speaking as Kun's firstmate: closing this as stale. It has been waiting on a contributor update for 14+ days with no author push or comment. Reopen if you want to pick it back up. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Productize the already live-proven Firefox web-ext + Marionette/WebDriver route as a reusable, harness-agnostic Firstmate tool plus a just-in-time agent skill. The captain explicitly authorized adding this capability to the shared tool set.
Deliverable: one new shell CLI bin/fm-firefox.sh that owns the lifecycle of a single isolated, disposable Firefox process driven over Marionette. Deliberate architecture decisions (not mistakes): (1) chose a thin harness-agnostic shell CLI over a new MCP server, AXI daemon, or global service, because the tool-layer-placement scout established that harness-agnostic CLI surfaces take precedence and every Pi worker already inherits the same MCP config, so a daemon would add a second browser control plane with no reach benefit; (2) the single supported install route is Marionette Addon:Install(temporary) so the tool can own exactly one -no-remote Firefox process it launched, rather than delegating launch to web-ext (which spawns its own process); web-ext is intentionally only reported by doctor as an optional, unmanaged alternative, not used; (3) the Marionette client is a small raw-protocol python3 implementation embedded in the CLI, so the tool's only hard deps are a Firefox binary and python3 (no marionette_driver dependency); marionette_driver is used only by the opt-in live test as an independent verification client.
Safety contract implemented: always create a fresh, empty, task-owned profile (never attach to, copy, or enumerate a live Firefox/Zen profile); self-allocate a loopback Marionette endpoint and refuse the shared default 127.0.0.1:2828; launch exactly one -no-remote Firefox; apply -remote-allow-system-access only to that disposable process and only when --system-access is requested; expose truthful machine-readable JSON receipts; and on stop tear down only the process/profile/port it created after proving the recorded PID is still our Firefox by its profile path, refusing ambiguous PID/port/profile ownership rather than guessing or killing an unrelated process.
Cookie/privacy scope is deliberately truthful, not a guarantee the tool can make: the CLI proves its own Firefox profile is fresh and task-owned but states in its receipt/help that a project-specific native messaging host can still independently discover unrelated Gecko cookie profiles (e.g. Zen) unless the project supplies its own verified cookie-off contract. The tool provides a generic caller env/cookie-off hook (--env, --cookie-off KEY=VALUE) that it forwards into the launched process without interpreting it, recording only the keys (never values) as a caller-asserted contract, so no MediaGrabber business logic is baked into Firstmate. Constraints honored: does not depend on the still-open MediaGrabber PRs #8/#9; never claims generic browser isolation controls project native-host cookie behavior; does not modify any global Pi/MCP configuration; the tool is invoked purely through the shell and is harness-independent.
Also added: an agent-only .agents/skills/firefox-marionette skill (with one one-line AGENTS.md section 13 load trigger) teaching when to choose this route over Playwright/CDP/FoxMCP, the browser-vs-OS boundary, the no-live-profile safety contract, dependency checks, receipts, cleanup, and the native-host cookie caveat, pointing to the CLI --help rather than duplicating flags; operator documentation at docs/firefox-marionette.md and current verification evidence at docs/verification/firefox-marionette.md, both registered in docs/documentation-audiences.json, plus a docs/scripts.md toolbelt row; and registration of both new tests in bin/fm-test-run.sh (hermetic in pure-contract-unit, live in live-harness-optin) with shard weight hints. Two tests were added: a hermetic command-surface test (tests/fm-firefox-marionette.test.sh) that drives a fake Firefox speaking the real Marionette wire format and asserts observed behavior and receipt fields only (never implementation source bytes) - covering dependency failure, clean-profile creation, loopback allocation, ownership refusal, extension-path validation, truthful receipts, exact cleanup, interruption/error cleanup, and the native-host cookie caveat/caller-hook contract; and an opt-in live test (tests/fm-firefox-marionette-live-e2e.test.sh, gated by FM_FIREFOX_LIVE_E2E=1) that uses a synthetic local extension and a loopback HTTP page (no external URL) to prove real add-on install plus web-content and extension/browser-context control without pixel clicks, verifies no existing Firefox/Zen process or the shared endpoint changed, and leaves no Firefox child/profile/port behind, self-skipping when Firefox/python3/WebDriver deps are absent so standard CI stays hermetic. The live proof was run on this Mac against Firefox 153.0.3.
What Changed
bin/fm-firefox.sh, a harness-agnostic shell CLI that owns the full lifecycle of a single isolated, disposable Firefox driven over Marionette: it always creates a fresh task-owned profile, self-allocates a loopback Marionette endpoint (refusing the shared127.0.0.1:2828), launches exactly one-no-remoteprocess, installs the addon via a small embedded raw-protocol python3 Marionette client (so hard deps are only a Firefox binary + python3), exposes truthful JSON receipts/doctoroutput, forwards an uninterpreted--env/--cookie-offcaller hook (recording keys only), and onstoptears down only the process/profile/port it created after verifying the recorded PID is still our Firefox by profile path. A start-time INT/TERM/EXIT trap kills the launched pid and removes the fresh profile if interrupted before the session state file is committed, closing the orphaned-process/profile window..agents/skills/firefox-marionetteagent skill (with an AGENTS.md section 13 load trigger), operator docs atdocs/firefox-marionette.md, verification evidence atdocs/verification/firefox-marionette.md, both registered indocs/documentation-audiences.json, and adocs/scripts.mdtoolbelt row.bin/fm-test-run.sh: a hermetic command-surface test (tests/fm-firefox-marionette.test.sh) that drives a fake Firefox speaking the real Marionette wire format and asserts observed behavior plus receipt fields (dependency failure, clean-profile creation, loopback allocation, ownership refusal, extension-path validation, exact/interrupted cleanup, native-host cookie caveat), and an opt-in live e2e test (tests/fm-firefox-marionette-live-e2e.test.sh, gated byFM_FIREFOX_LIVE_E2E=1) that proves real add-on install and web/extension-context control against a loopback page, verifies no existing process or the shared endpoint changed, and self-skips when deps are absent so standard CI stays hermetic.Risk Assessment
✅ Low: Artımlı değişiklik, Round 1'deki tek uyarıyı (interrupt-penceresi süreç/profil sızıntısı) doğru bir INT/TERM/ERR trap + in-flight teardown ile kapatıyor; ampirik olarak trap'in sinyalde tetiklendiği, lokalleri dinamik kapsamla gördüğü ve açık hata yollarını bozmadığı doğrulandı, ayrıca doğrudan bir hermetik test eklendi.
Testing
Ran the smallest intent-relevant tests: the hermetic fm-firefox-marionette test (14 checks, real Marionette wire protocol against a fake Firefox — dependency refusal, clean owned profile, loopback allocation + 2828 refusal, truthful receipts, ownership-proven refusal, extension-path validation, exact cleanup, interruption/error/SIGTERM teardown, native-host cookie caveat) and the opt-in live e2e against the real Firefox 153.0.3 named in the intent (real temporary add-on install, web-content + extension-context control over Marionette with no pixel clicks, proof that no unrelated Firefox/Zen process or the shared 2828 endpoint changed, and zero leftovers). Both exit 0. The doctor JSON surface was exercised directly and reports honestly. This is a shell CLI with no rendered UI surface, so product-level evidence is the live-e2e CLI transcript and JSON receipts rather than a screenshot. No transient artifacts remained (default state root empty, worktree clean).
Evidence: Live e2e transcript vs real Firefox 153.0.3
ok - fm-firefox live E2E (Mozilla Firefox 153.0.3): real add-on install, web-content and extension-context control, no unrelated browser touched, no leftoversEvidence: Hermetic command-surface test output (14 checks)
Evidence: doctor --json product surface
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 1 issue found → auto-fixed ✅
bin/fm-firefox.sh:477- start komutunda sinyal (SIGTERM) kesinti penceresi için temizlik yok: 477. satırda arka planda (disown edilmiş) Firefox başlatıldıktan sonra, 518. satırda state dosyası yazılana kadar (--timeout kadar, varsayılan 45sn readiness + extension install süresi boyunca) script'te hiçbir trap yok. Bu pencerede fm-firefox.sh PID'ine SIGTERM gelirse (ör. çağıran fleet worker'ını sonlandıran bir supervisor — bu araç tam olarak fleet worker'ları tarafından çağrılmak üzere tasarlı), script ölür ama disown edilmiş Firefox yetim kalır ve profil dizini state root altında kalır; state dosyası hiç yazılmadığı için 'stop' bunu asla geri alamaz. Bu, aracın merkezi 'disposable / no-leftovers / owned-only' garantisini interrupt yolunda ihlal eder. Hermetik test 8c/8d/8e yalnızca Firefox-tarafı hata yollarını kapsıyor, gerçek bir sinyal kesintisini değil. Öneri: start süresince aktif bir INT/TERM/EXIT trap ekleyip, session commit edilene (state dosyası yazılana) kadar başlatılan pid'i öldürüp taze profili silen bir in-flight cleanup ekleyin. Not: Ctrl-C (process group'a SIGINT) senaryosunda Firefox aynı pgroup'ta olduğu için zaten ölür; boşluk yalnızca script PID'ine doğrudan SIGTERM gönderilen durumdadır.🔧 Fix: trap interrupted fm-firefox start to prevent orphaned process/profile
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
bash tests/fm-firefox-marionette.test.sh— hermetic command-surface, 14/14 checks pass, exit 0FM_FIREFOX_LIVE_E2E=1 bash tests/fm-firefox-marionette-live-e2e.test.sh— live proof vs real Firefox 153.0.3, exit 0bin/fm-firefox.sh doctoranddoctor --json— truthful dependency/receipt product surface (firefox+python3 resolved, web-ext optional/unused)Manual before/after browser snapshot: pre-existing Zen (109 procs) and Firefox pid 890 untouched; no fm-firefox state/profile/port leftovers; worktree clean✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.