Add circuitforge_core.sync:
- SyncStore: SQLite-backed blob store with per-user, per-data-class consent gating.
Push rejected if user hasn't opted in to that data class (SyncPref). Pull returns
only consented classes. Last-write-wins by updated_at timestamp (equal ts
does not regress existing data).
- SyncConfig: env-based config factory; reads CF_SYNC_* env vars namespaced per
product (e.g. CF_SYNC_PEREGRINE_DB).
- make_sync_router(): returns a FastAPI APIRouter pre-wired with:
GET /status — last sync timestamps per data class (any tier)
GET /prefs — list consent settings (any tier)
PATCH /prefs — opt in/out of a data class (any tier)
POST /push — upload blob (Paid+ only)
GET /pull — download blobs (Paid+ only)
DELETE /blob/{data_class} — delete one blob (any tier)
DELETE /wipe — clear all blobs + prefs (any tier)
- 45 tests covering consent gating, last-write-wins, blob opacity, tier checks,
and all HTTP endpoints.
Also: feedback.py bugbot token fallback (FORGEJO_BOT_TOKEN → FORGEJO_TOKEN);
pyproject.toml: add sync optional extra.