"""Prefect entry points for the corpus pipeline. `docs/phase-4-corpus.md` §4.4: *"Prefect deployment on the existing Prefect server; worker only, do not deploy a second server. Trigger: git push to content/, plus a nightly audit."* Prefect is optional at runtime. If it is not installed, these functions are plain callables and the same code runs straight from the CLI (`python3 -m corpus.cli sync`), which is what `make corpus` does. Nothing about the pipeline depends on the orchestrator, and the orchestrator depends on nothing but the pipeline. The deployment commands (run on `.13`, which already has the Prefect server) are in `docs/phase-4-notes.md` §6. This file is only the work definition. """ from __future__ import annotations import sys try: # pragma: no cover - exercised only where Prefect is installed from prefect import flow as _flow from prefect import get_run_logger as _get_run_logger PREFECT_AVAILABLE = True except ImportError: # pragma: no cover PREFECT_AVAILABLE = False def _flow(*_args, **_kwargs): def decorator(func): return func return decorator def _get_run_logger(): class _Logger: def info(self, message, *args, **kwargs): print(message, file=sys.stderr) warning = error = info return _Logger() from corpus import freshness, pipeline # noqa: E402 (after the Prefect shim) from corpus.config import Config # noqa: E402 @_flow(name="corpus-sync", log_prints=True) def sync_corpus() -> dict: """Incremental refresh. Runs on every push that touches content/.""" logger = _get_run_logger() cfg = Config.from_env() stats = pipeline.run(cfg, mode="sync", log=logger.info) return stats.to_dict() @_flow(name="corpus-rebuild", log_prints=True) def rebuild_corpus() -> dict: """From-scratch rebuild. The recovery path: deleting the volume is always safe.""" logger = _get_run_logger() cfg = Config.from_env() stats = pipeline.run(cfg, mode="rebuild", log=logger.info) return stats.to_dict() @_flow(name="corpus-audit", log_prints=True) def freshness_audit(alert: bool = True) -> dict: """Nightly audit. Reports stale documents and index drift, then alerts.""" logger = _get_run_logger() cfg = Config.from_env() report = freshness.audit(cfg, log=logger.info) logger.info(freshness.render_text(report)) if alert and not report["ok"]: try: freshness.alert(report, cfg.apprise_url) except Exception as exc: # noqa: BLE001 - the alert must not fail the audit logger.warning(f"Apprise alert failed: {exc}") return report if __name__ == "__main__": # pragma: no cover - convenience for a manual run command = sys.argv[1] if len(sys.argv) > 1 else "sync" if command == "audit": print(freshness.render_text(freshness_audit(alert=False))) elif command == "rebuild": print(rebuild_corpus()) else: print(sync_corpus())