Ghost Fetch
Under maintenancePricing
Pay per usage
ghost-fetch now keeps only the backbone — resolve Context → pure Recovery Policy ladder → gateway transports → Verifier Verdict → envelope. Three systems were removed whole:
src/strategy-learning.ts): Thompson sampling, the per-arm Beta
posterior, reward and label machinery. Selection is now a deterministic ladder — HTTP first when
dialable, escalate to the browser tier otherwise. Supersedes ADR-0002.src/recovery-playbooks.ts): the two profile-evidence rules
(skipFailedClearanceReplay, rotateAfterRepeatedFailedBrowserRecovery). Every branch they
overrode now takes its baseline action. The pure decide() seam and its action alphabet are
unchanged (ADR-0001).src/profiles.ts): the HostContextProfileV1 materializer, the
profile KV store, the profiles:* scripts, and GHOST_FETCH_PROFILE_KV_STORE_ID. Supersedes
ADR-0003.No behaviour change given an empty profile store: the bandit selector and the playbook rules
were both live code on every fetch (playbooks default-on, selector default upgrade_only), but
inert without a materialized profile for the fetched context — no profile → the selector hits
empty_posterior and the playbook gates fail, so both fall through to the deterministic ladder. The
store was populated only by the offline profiles:* scripts; an unseeded deployment always took the
ladder. A deployment that had seeded profiles could see a different action sequence — the same
reachable verdict in the common case, though at a max_attempts/max_cost_units boundary a skipped
attempt could flip the outcome — so the removal is safe on the empty-store basis rather than on any
config flag.
Backward compatible. policy.strategy_selection is still accepted and is now a deprecated
no-op (ignored). The envelope still carries strategy_learning with its full pre-refactor field
set — mode, selected_initial_strategy, actual_initial_strategy, selection_reason, gate,
reward_direct, reward_chain, posterior_snapshot — filled with honest no-bandit values (empty
gate, rewards 0, empty posterior), so a v1 consumer's strict schema still validates. The shape is
compatible; the values are not identical to the old upgrade_only default (a consumer that branched
on mode/posterior_snapshot values rather than reading the fields sees a change).
TRACE_SCHEMA_VERSION is unchanged at 4. Telemetry remains as off-by-default observability, now
with nothing consuming it.
country semantics changesrc/context.ts owns the Context tuple and its 5-part key
(host|country|method|path_group|proxy_group); the orchestrator resolves it once
per fetch and threads it into selection, recovery playbooks, and telemetry.country: "ANY" (or PROXY_COUNTRY) — the egress actually used — instead of the
invented "US" default. Historical v1 rows with country: "US" are ambiguous
(explicit US vs the old default). Structurally compatible; no schema-version bump.host|us|… while selection and playbooks read host|any|….host: "unknown" instead of "".GHOST_FETCH_TELEMETRY_RAW=1 attaches the full raw fetch result — the same payload /v1/fetch returns, including the HTML body, response headers, final_url, decision trace, and base64 screenshot — to the request telemetry event under a raw_response field. Off by default; sanitized telemetry is unchanged (backward compatible).GHOST_FETCH_TELEMETRY_RAW and a valid custom GHOST_FETCH_TELEMETRY_DATASET_ID, so raw payloads (bodies, set-cookie headers, full URLs) never reach the shared default ghost-fetch-telemetry dataset that feeds Passive Context Profiles.GHOST_FETCH_TELEMETRY_DATASET_ID is now validated as an Apify Dataset id (17-char alphanumeric) or name (name / username/name); a malformed value warns and falls back to the default dataset instead of wedging writes. The dataset open is also no longer cached on failure.body is bounded by GHOST_FETCH_TELEMETRY_RAW_MAX_BYTES (default 6 MB, fits Apify's ~9 MB item limit); when truncated the event carries raw_response_truncated: true.pip install "cloverlabs-camoufox[geoip]" + python3 -m camoufox fetch official/150.0.2-alpha.26.rt:c captcha. cloverlabs FF150 → auto-solvable rt:i interstitial.fox_main_world_eval request paramcloverlabs/FF150 has no Juggler-scope isolation (single-world model). The flag set camoufox's allowMainWorld:True which is now inert; the mw: prefix it was meant to enable is parsed as a JavaScript label. The param has no observable effect on this build and is dropped from the request schema and the FoxOptions interface. See proxy-mcp v3.3.0 for the underlying world-model investigation and scripts/camoufox-world-probe.ts to verify on any installed build.
inPageFetch no longer hidden from page hooksinPageFetch() (src/fetch.ts:498) issues fetch() via page.evaluate to avoid Akamai poisoning of GET navigation. Under daijro/FF135 the eval ran in a Juggler scope, isolated from any page-installed window.fetch hooks. Under cloverlabs/FF150 it runs in the page's main world — pages that have hooked window.fetch (Akamai, PerimeterX) now observe the request. No correctness change; potential detection signal on high-tier WAFs.