diff --git a/.dispatcher.yaml b/.dispatcher.yaml new file mode 100644 index 0000000..f994e25 --- /dev/null +++ b/.dispatcher.yaml @@ -0,0 +1,134 @@ +# .dispatcher.yaml — contract between a project and signal-dispatcher. +# Lives in the ROOT of the project's own repo (here: goon-foss/goon), versioned +# with the project. `dispatcher project-sync ` loads it into the DB. + +schema_version: 1 + +project: + name: goon + description: > + Self-hosted aggregator of adult (18+) scene metadata. FastAPI backend + + APScheduler worker on Postgres 16. Ingests from TPDB and StashDB on a delta + cron, cross-source dedup by perceptual hash + title + performer, plus a + performer-driven backfill that scrapes ~25 public tube sites. On-demand + stream resolution via yt-dlp and a P.A.C.K.E.R. unpacker. Expo / React + Native mobile client (Android). + keywords: [goon, tpdb, stashdb, tube, scene, performer, phash, yt-dlp, packer] + +repo: + url: https://github.com/goon-foss/goon + default_branch: public/main # local `main` is a stale divergent line; prod work happens on public/main + branch_prefix: auto-fix/ + pr_label: auto-fix + require_ci_green_before_merge: true + +deploy: + mode: manual # no CD: deploy = scp + `docker compose restart` + # Consequence: a merged PR is NOT live until a human deploys. The dispatcher + # keeps the issue in `verifying` (deploy_pending=true) and closes it only once + # Sentry actually goes quiet — never on merge alone. + +worktree: + base: /opt/dispatcher/worktrees/goon + setup: + - python3.12 -m venv .venv + - .venv/bin/pip install -e ".[dev]" + +ci: + # -x stops at the first failure: fast for the fix loop. Note CI runs the full + # suite (`pytest --tb=short`), so CI can surface failures this command hides. + test_cmd: .venv/bin/pytest --tb=short -x -q + lint_cmd: .venv/bin/ruff check app/ tests/ + pre_test: [] # tests use respx HTTP mocks + fixtures, no DB + post_test: [] + timeout_min: 10 + +sources: + sentry: + # org_slug injected from env (SENTRY_ORG) — kept out of source to avoid linking + # this public repo to a specific Sentry org identity. + project_slug: goon # backend. Mobile (RN) is a SEPARATE Sentry project: `android` + region_url: https://de.sentry.io # EU region — Sentry's API is region-scoped + environments: [production] + severity_map: {error: med, warning: low, fatal: high, info: low} + ntfy: + topics: [] # goon emits no custom alerts today + slack: null + +path_policy: + # Tube scrapers: a broken selector means the SITE changed, not the code. An + # LLM can write a plausible new selector but cannot know the correct one + # without the live page — and the test fixture IS the HTML that just broke. + # So: open a PR as a HYPOTHESIS, never auto-merge, a human verifies live. + - paths: [app/connectors/direct_scrapers/, app/extractors/] + mode: pr-hypothesis + # Cross-source dedup: regressions here are genuine code bugs, but subtle. + # Real fix attempts, but always PR — never auto-merge. + - paths: [app/resolve/] + mode: pr-only + # Everything else (app/api/, app/scheduler/, ...) follows the policy table. + +forbidden_paths: + - alembic/versions/ # DB migrations — auto-edit is catastrophic + - .github/workflows/ + - deploy/ + - docker-compose.yml + - Dockerfile + - pyproject.toml # dependency bumps are a separate decision + - mobile/ # separate pipeline (OTA via scripts/publish_update.py) + - app/static/ # APK + OTA bundle artifacts land here + +protected_branches: [main] + +fix_constraints: + max_files_changed: 5 + max_lines_changed: 200 + +knowledge: + doc_paths: [README.md, CONTRIBUTING.md, DEPLOY_BACKLOG.md, DISCLAIMER.md] + # Injected into the classifier prompt. Derived from goon's live Sentry + # (23 unresolved issues, 2026-05-22): transient upstream noise dominates. + triage_hints: > + Transient upstream failures dominate goon's Sentry and are NOT bugs: + HTTPException with "upstream 4xx/5xx", "proxy error", "extraction failed + temporarily", "img/tube fetch failed", "All connection attempts failed" — + a remote site or CDN failed, not goon. Classify intent=noise unless one + culprit sustains a high event rate. Smoke-test / health-check events + ("Smoke test ...", "Sentry init OK") are intent=noise, severity=low. + Genuine bugs (intent=bug): NameError, ModuleNotFoundError, TypeError from a + signature/constructor mismatch, psycopg IntegrityError / UniqueViolation in + dedup or upsert paths, and encoding errors (UnicodeEncodeError). Read the + UNDERLYING error, not the HTTPException prefix — an "img fetch failed" + title can wrap a real UnicodeEncodeError bug. + conventions: > + Code identifiers and type hints are English; some inline comments are + Polish. curl_cffi is pinned <0.15 until yt-dlp ships a compat release + (see pyproject.toml). The performer-driven worker walks performers by + last_searched_at NULLS FIRST — "completeness over recency" is intentional. + Sentry before_send drops upstream 502/503/504, so absence of an alert is + not proof of a fix. + +# Seeds the `policies` table on `dispatcher project-sync`. Conservative on +# purpose: goon starts fully PR-gated. Flip low/med Sentry bugs to +# auto-merge-on-green only after the classifier proves reliable. +policies: + - priority: 50 + match_severity: [critical] + ship_mode: pr-and-ntfy + notify: [ntfy:urgent] + max_attempts: 1 + verify_window: "1 hour" + - priority: 60 + match_severity: [high] + ship_mode: pr-and-ntfy + notify: [ntfy:default] + max_attempts: 2 + verify_window: "1 hour" + - priority: 100 + match_severity: [low, med] + match_intent: [bug, data_quality] + match_source: [sentry] + ship_mode: pr-and-ntfy + notify: [ntfy:default] + max_attempts: 2 + verify_window: "30 minutes"