All four tasks done. Here's a summary of every change made: --- S1 — Persist scraped snapshot fk_scraper.py: - Added persist_callback: Callable[[ParentalLeaveFetch], Awaitable[None]] | None field to _SessionState - Added persist_callback parameter to start_session() - Updated _do_extract(): after successful extraction, awaits the callback; on callback failure sets status=error instead of done routers/fetch_data.py: - Added parental_leave_repo: ParentalLeaveRepositoryDep to start_fetch - Created a _persist closure capturing the repo and passed it to start_session() — the shared app.state.db_connection is safe to use from the background coroutine (aiosqlite serialises all operations through its internal queue) S2 — Read contract (Option A) No backend changes needed. The existing GET /api/parental-leave/{personal_data_uuid}/{child_id} route (fetch_data.py:81) already calls get_latest_by_parent which orders by fetched_at DESC with no source filter — it returns the latest snapshot regardless of whether it's scraped or manual. The junior should point api.ts to exactly that path; both IDs come from GET /api/auth/context. S3 — Scraper enabled - backend/.env created with APP_SCRAPER_ENABLED=true (gitignored — enables the scraper for the running dev app) - backend/.env.example created as documentation - Stale "DO NOT SHIP / pending legal review" docstring in fk_scraper.py replaced with "legal cleared 2026-06" + guardrail summary - _SCRAPER_DISABLED 501 message updated (no longer says "pending legal review") - config.py comment updated to reference APP_SCRAPER_ENABLED=true and note playwright install chromium requirement Tests (S1 acceptance) 3 new tests in test_fk_scraper.py: - test_do_extract_calls_persist_callback_on_success — unit: callback is invoked, status is done, result is set - test_do_extract_sets_error_when_persist_callback_fails — unit: callback failure → status=error, not done - TestPersistenceIntegration::test_scraped_row_is_readable_after_session_done — integration: real SQLite repo, row persisted, get_latest_by_parent returns the scraped row Full suite: 178 passed (pre-existing 1 failure in test_tips_service.py unchanged).