goon/.dispatcher.yaml
https://github.com/goon-foss/goon 60ece70708 add .dispatcher.yaml — signal-dispatcher contract
Declares this repo to the signal-dispatcher (auto-triage + fixer for prod
errors). Sentry source is goon-foss/goon (EU region); fixer opens PRs to
public/main with auto-fix/ branch prefix, never auto-merges, and the
verifier waits for the source signal to go quiet (not just PR merge).

Conservative policy: all severities open PRs only; nothing auto-merges
until the classifier proves reliable.
2026-05-23 17:23:14 +02:00

134 lines
5.6 KiB
YAML

# .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 <path>` 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"