"""The shared source resolver (phase 35, task 03; local kind, phase 38). One function, two callers: the in-app Sync pipeline (``app/api/sync.py::_run_sync``) and the CLI (``scripts/import_docs.py::_resolve_sources``) — both resolve the ``git_sources`` rows to import through :func:`effective_sources`, so the admin-managed ``git_sources`` table is what actually gets cloned (git rows) or walked directly (local rows) from either entry point (the API's ``GET /api/git-sources`` fallback list is the only other place that reads the env list — task 02). Precedence (the phase's locked decision — the env var is demoted, not removed): ``git_sources`` DB rows of **both kinds** win, in ``(added_at, id)`` order; ``BOR_GIT_SOURCES`` is a fallback only while the table is empty (git URLs surface as synthetic ``kind='git'`` rows — the env fallback is git-only); both empty → ``([], "env")`` and each caller keeps its existing fail-loud behavior (sync: ``GitSyncError``; CLI: the legacy ``DEFAULT_SOURCES`` fallback). :func:`effective_git_sources` is kept as the phase-35 back-compat alias (repo URLs of the effective git rows) so existing importers of the old name keep working; new code calls :func:`effective_sources` and branches on ``row.kind``. """ from __future__ import annotations from typing import Literal from sqlalchemy import select from sqlalchemy.orm import Session from app.config import get_settings from app.models import GitSource def effective_sources(db: Session) -> tuple[list[GitSource], Literal["db", "env"]]: """``(rows, origin)`` — the effective source rows (both kinds) and where they came from. ``"db"``: the ``git_sources`` rows — ``kind='git'`` (repo URL to clone/pull) and ``kind='local'`` (existing directory to walk directly) — in ``(added_at, id)`` order (oldest first, id tie-break for same-timestamp inserts). Once the table has any row, ``BOR_GIT_SOURCES`` is ignored entirely. ``"env"``: the ``BOR_GIT_SOURCES`` list — ``Settings.git_source_list``, the phase-28 CSV parse, reused not re-implemented — surfaced as synthetic ``kind='git'`` rows (``path`` NULL; the env fallback is git-only), used only while the table is empty; both empty → ``([], "env")``. """ rows = db.scalars( select(GitSource).order_by(GitSource.added_at.asc(), GitSource.id.asc()) ).all() if rows: return list(rows), "db" return [GitSource(url=url, kind="git") for url in get_settings().git_source_list], "env" def effective_git_sources(db: Session) -> tuple[list[str], Literal["db", "env"]]: """Back-compat alias for the phase-35 name (existing importers). Returns the repo URLs of the effective ``kind='git'`` rows only, with the same origin label — the env fallback is git-only, so its behavior is unchanged. Local rows carry no URL (they are walked directly); new code should call :func:`effective_sources` and branch on ``row.kind``. """ rows, origin = effective_sources(db) return [row.url for row in rows if row.kind == "git"], origin