# 3eegitim-outage-bot

Sentry hatalarını YouTrack **OPS** ticket'ına ve Discord'a taşıyan Cloudflare Worker. Dakikada bir cron ile poll eder; webhook almaz.

Kaynak: puanbenim'in `outage-bot`'undan uyarlandı. Bu proje **ayrı bir worker** — puanbenim'in bot'una dokunulmaz (ayrı YouTrack instance'ı, ayrı Sentry projeleri, ayrı Discord kanalları, ayrı KV).

```
https://3eegitim-outage-bot.sets-0e8.workers.dev   (health endpoint)
cron: */1 * * * *
```

## İki hat

**1. Hata hattı (aktif).** Sentry'de çözülmemiş `error`/`fatal` issue'ları çeker → her yeni `(ortam, issue)` çifti için OPS ticket'ı açar → Discord'a bildirir.

- **Dedup KV'de**, anahtar `error:<environment>:<issueId>`. Aynı hata `local` ve `production`'da ayrı ticket alır; ikinci poll döngüsü no-op'tur.
- Stack trace ticket açıklamasına **gömülür** — Sentry'ye erişilemese bile ticket kendi kendine yeter.
- Discord bildirimi **KV kaydından sonra** ve best-effort gönderilir. Discord düşerse ticket yine açılmış ve dedup yazılmış olur; aksi halde her cron döngüsü aynı ticket'ı tekrar açardı.

**Kapanış geçişi.** Her cron döngüsünün sonunda KV'deki hata kayıtları taranır: ticket'ı **kapanmışsa** (YouTrack'in kendi `resolved` damgası — `Fixed`, `Duplicate`, `Obsolete`, hepsi) Sentry kaydı da `resolved` işaretlenir ve kayda `sentryResolvedAt` yazılır.

- ⛔ Neden var: iki yarı birbirinden ayrılmıştı. Bot her çözülmemiş hataya ticket açıyor, ticket kapandığında Sentry tarafını **hiçbir şey kapatmıyordu**. 22.08.2026'da sayıldı — 3e-egitim'in 24 açık kaydından 14'ü haftalar önce kapanmış ticket'lara aitti. Çoğunluğu bitmiş işten oluşan bir liste okunmaz hâle gelir ve gerçekten tekrarlayan hata onun içinde kaybolur.
- ⚠ `resolved`, `ignored` değil: Sentry çözülmüş bir kaydı tekrar düştüğünde **regresyon** olarak yeniden açar — istenen sinyal tam olarak budur. Yok sayılan kayıt sessiz kalır.
- ⚠ KV damgası sayesinde bir kayda bir kez bakılır; damgasız hâlde her döngü her hatayı YouTrack'e sorardı.
- ⚠ `SENTRY_API_TOKEN`'ın **yazma izni** olmalı (issue durumu değiştirme). İzin yoksa kapatma sessizce başarısız olur, kayıt damgalanmaz ve bir sonraki döngüde tekrar denenir — yani eksik izin veri bozmaz, yalnız işi yapmaz.

**2. Kesinti hattı (şimdilik kapalı).** Sentry uptime + site check + Hetzner status → OPS ticket'ı + kurtarma doğrulaması.

`SITE_CHECK_ENABLED=false` — uygulama henüz deploy edilmedi. Açık bırakılırsa bot her dakika "site erişilemiyor" diye ticket açar. Deploy'da `true`'ya çekilecek (EGT-47).

## Discord yönlendirmesi

| Ortam | Kanal |
|---|---|
| `production` | `#prod-debug` |
| `local`, `local-prod` | `#debug` (mesaj başında `[ortam]` etiketiyle) |

`#prod-office`, `#prod-customer`, `#prod-accounting` kanalları **bu bot'un işi değildir** — onlar uygulama içi iş olayı bildirimleridir (Laravel'den çıkar, Sentry'ye uğramaz).

## Yapılandırma

Ayarlar `wrangler.jsonc` içindeki `vars`'ta. Gizli değerler **secret** olarak tutulur:

```bash
npx wrangler secret put YOUTRACK_TOKEN
npx wrangler secret put SENTRY_API_TOKEN
npx wrangler secret put DISCORD_WEBHOOK_DEBUG
npx wrangler secret put DISCORD_WEBHOOK_PROD_DEBUG
```

`.dev.vars` yalnız yerel geliştirme ve `wrangler types` içindir — sahte değerler taşır, gitignore'dadır.

## Geliştirme

```bash
npm install
npx wrangler types      # Env tipini vars + .dev.vars'tan üretir
npx tsc --noEmit        # tip kontrolü
npm test                # vitest (Workers pool) — 50 test
npx wrangler deploy
```

Cloudflare kimliği ortamdan okunur: `CLOUDFLARE_API_TOKEN` (= `~/.zshrc`'deki `CF_API_TOKEN`) ve `CLOUDFLARE_ACCOUNT_ID`.

## Doğrulama

Sentry'ye elle bir olay gönderip bot'un ticket açmasını izleyebilirsiniz — kurulumda böyle doğrulandı: `[local]` etiketli OPS ticket'ı açıldı, stack trace gömülü, Sets'e atandı. Test ticket'ı sonrasında kapatılmalıdır.
