docs/04-engine-design.md §7's failure policy table has said "403 after redirect: retry once with the original referrer; many CDNs require it" since it was written, and Error::forbidden's own enum comment says the same -- but grepping download_task.cpp and http_client.cpp for 403 turned up nothing. It was never built. Implemented at both points a 403 can surface: - The probe (net::Prober, a separate request path from segment workers): on_probe_result() now retries once via restart_probe(false), with effective_referrer set to the download URL's own origin (origin_of(), via net::split_url()), when the failure is Error::forbidden and this is the first retry. A second 403 asks rather than fails outright -- auto_pause_locked(..., false, true), the same "ask, don't just fail" path 416/etag-mismatch already use -- specifically so DownloadHandle:: refresh_url() stays usable afterward (its own contract requires a non-terminal task); this is what makes the expiring-signed-url mode's README-documented refresh_url() recovery actually reachable. - Each segment worker (SegWorker::forbidden, set in seg_head() on a 403 HEAD): the same one-shot referrer retry via retry_worker(), landing on auto_pause_locked() on a second 403 for the same reason. Both paths route the retry's Referer through a new effective_referrer field rather than spec.referrer directly, since the origin-retry must not overwrite what the caller actually asked for -- start_worker_locked() and restart_probe() were switched to send effective_referrer instead. do_refresh_url() had two latent bugs surfaced by actually exercising the expiring-signed-url recovery path end-to-end: 1. It unconditionally proceeded to resume even when the refresh probe itself failed -- a bad refresh URL would silently un-pause a task with nothing behind it. Now returns (stays paused) on !r.has_value(). 2. It only handled "already probed once, just refreshing a few fields" -- for a task whose first-ever probe never succeeded (every hostile mode this commit adds a test for that pauses at the initial probe, not mid-download), s->registered was never true, so the existing `if (s->registered) set_want()` never fired and nothing happened. Now detects !s->have_probe and calls finish_probe_locked() directly, the actual first-time registration/segmenter-construction path. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Q3QrF7rCt21bkAjt9BCDFQ
Owner: lane CORE. See ../docs/agents/AGENT-CORE.md and ../docs/04-engine-design.md. No JSON, no SQL, no Qt, no RPC in this tree.