Trigger Backfill
Trigger a REAL backfill sync run for a connector.
Task LB4: this used to return a fake “queued” job with a made-up
estimated_completion timestamp — nothing ever executed, progress
never advanced past 0.0, and result_url was always null. Replaced with
the exact same real-execution path POST /connectors/{id}/sync uses
(run_sync() via services/connector_sync.py), just with
trigger="backfill" and an explicit since override instead of
trigger="manual"’s cursor-based incremental read. The response IS the
real sync_run record (same shape trigger_sync returns) — no
separate “job” concept, no fabricated ETA. It’s immediately queryable via
GET .../sync-runs (this run appears with trigger="backfill") and
GET .../sync-runs/{run_id}, and on failure (e.g. SSRF-blocked or
unreachable DSN) the run is persisted with status="failed" and a real
error — never a fake queued success.
since-semantics: the historical window comes from the connector’s own
backfill_days config (set at POST /connectors time, default 30 —
see ConnectorCreate/allowed_fields above), not a request body —
there’s nothing left for a caller to override, so the endpoint takes no
body. backfill_days (1..730 per _validate_backfill_days) ->
since = now - backfill_days (an explicit lower bound that OVERRIDES
last_synced_at for this one run — see run_sync’s docstring for why
a backfill intentionally re-walks a window that may predate the
cursor). Missing/None/0 (a legacy connector predating this
validation, or an explicit “unset” 0) resolves to the documented
create-time default of 30 days, NOT a full/unbounded read: an
unbounded historical read is NOT reachable via this endpoint (contrast
db_adapter.db_fetch_rows’s own “None -> full read” semantics, which
apply only when a provider is called directly with since=None — this
handler never passes that through). Out-of-range/non-numeric stored
values are defensively clamped into [1, 730] with a warning; they
should never occur post-validation but stored data can predate it.