Skip to content

feat(engine): WorkLease reclaim and fail-closed challenge_block - #53

Merged
gildesmarais merged 5 commits into
mainfrom
feat/worklease-failclosed-challenge
Aug 30, 2026
Merged

feat(engine): WorkLease reclaim and fail-closed challenge_block#53
gildesmarais merged 5 commits into
mainfrom
feat/worklease-failclosed-challenge

Conversation

@gildesmarais

Copy link
Copy Markdown
Member

What changed

  • Add WorkLease (app/engine/work_lease.py) as sole owner of admit, deadline, and Chromium reclaim; delete soft future.cancel()-as-reclaim and ScrapeProgress.
  • Fail-closed: unclean / hung / mid-wait unreadable surfaces emit challenge_block (no soft-retry budget burn).
  • Per-host concurrency via SCRAPE_MAX_PER_HOST; abort flag skips Chromium after host-gate release on reclaim.
  • Shared challenge HTML fixtures under tests/fixtures/challenge/ for detector corpus tests.
  • Wire unchanged: still error_category + timeout_phase.

Why

Root cause

Hostile hosts (e.g. SoundCloud-class) burned ~45s as timeout/work, held executor slots after outer cancel, starved the pool (timeout/queue), and mapped to retryable GATEWAY_TIMEOUT (“took too long”) instead of BLOCKED_SURFACE.

Intent

Reclaim workers on lease end and emit challenge_block when the surface is blocked/hostile so gem → web Decision already yields honest non-retryable UX.

Risk

  • Driver kill fidelity under real Botasaurus/Chromium needs post-deploy observation.
  • Dual detector algorithms (Python vs Ruby) still differ; fixtures are aligned only.
  • Deploy scrape-api before expecting Sentry timeout_phase:queue drop / scrape.challenge_block rise.

Review map

  1. app/engine/work_lease.py — admit / deadline / reclaim / abort
  2. app/domain/scrape_service.py + app/engine/session.py — lease wiring and cold-boot reclaim race
  3. app/engine/browser_tier.py + app/infra/detector.py + app/engine/strategies.py — fail-closed challenge, soft-retry removed
  4. tests/domain/test_work_lease_reclaim.py + tests/engine/test_session_reclaim.py + challenge corpus tests

Validation

  • make check / make ready — exit 0 (189 tests)
  • Docker smoke against live Botasaurus — not run (deploy deferred)
  • Assure: Conditional residuals (pre-session reclaim + mid-wait unreadable) closed in 837bf85

Replace Future.cancel theater and ScrapeProgress with a single lease that
admits per-host, kills the session driver on deadline, and builds the
timeout envelope from one phase snapshot (SCRAPE_MAX_PER_HOST).
Delete soft-retry (may_retry_strategies) and prefer-challenge continue
paths so unclean or unreadable hung pages always emit challenge_block.
Own Cloudflare/DataDome/Vercel HTML under tests/fixtures/challenge,
trim one-sided detector markers, and document the shared corpus home.
Close idempotently by clearing the driver handle (not a pre-assign flag),
retain a strong ref to shielded executor work, and cover early reclaim.
Set lease.aborted on reclaim so workers exit without booting after host
gate release; treat unreadable mid-wait probes as challenge_block.
@gildesmarais
gildesmarais merged commit a17fc38 into main Aug 30, 2026
13 checks passed
@gildesmarais
gildesmarais deleted the feat/worklease-failclosed-challenge branch August 30, 2026 16:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant