# Slack → mail fallback (MC #107104)

# Slack → mail fallback (MC #107104)

Od 2026-08-18 `~/system/tools/slack.js` više ne gubi poruku kad Slack odbije da je primi.
Fallback je dodan **centralno**, u `cmdSend`, `cmdReply` i `cmdSendBlocks`, pa pokriva svih
~140 pozivalaca odjednom. Nijedan demon nije mijenjan.

## Zašto je uopšte trebalo

Workspace `alai-talk.slack.com` (6 korisnika) je na besplatnom planu i udara u limit poruka.
Greška `message_limit_exceeded` pojavljuje se u **41 log fajlu** pod `~/system/logs`, samo u
`ops-watchdog-error.log` **985 puta**. Zadnja datirana pojava je 2026-08-13; ništa ne objašnjava
zašto je stalo, pa može proraditi bez najave.

Šteta nije bila samo „izgubljen alert":

| demon | šta je gubio |
|---|---|
| `tldr-weekly-synthesis` | na Slack padu radi `process.exit(1)` i **baca cijeli sedmični memo** |
| `email-reactor-digest` | gubi dnevni digest u cijelosti |
| `tldr-watch`, `bilko-demo-uat`, `mail-boot-signal`, `lightrag-monitor` | samo alert; podaci prežive |

Mail fallback je jednom već postojao — `ceo-daily-digest.js` ga je uspješno okinuo 2026-08-13 —
pa je tiho uklonjen neispraćenom izmjenom 2026-08-16. Zato sada živi u `slack.js`, jedno mjesto,
umjesto da se ponovo raspe po pozivaocima.

## Ugovor: tri razdvojena stanja

Ovo je bio ključni dio dizajna. `tldr-weekly-synthesis` odlučuje hoće li **uništiti** svoj izlaz
na osnovu odgovora, pa „isporučeno" i „nigdje isporučeno" moraju biti razlučivi.

| izlaz | značenje | exit |
|---|---|---|
| `✓ Sent to #kanal` | Slack primio. **Tekst nepromijenjen** u odnosu na prije. | 0 |
| `⚠ SLACK_FAILED_MAIL_FALLBACK_SENT #kanal … (Message-ID …)` | isporučeno mailom | 0 |
| `⚠ SLACK_FAILED_MAIL_FALLBACK_DEDUPED #kanal …` | isti alert već mailovan u zadnjih 6 h | 0 |
| `ERROR: DELIVERY_FAILED_BOTH #kanal: …` (stderr) | **nigdje isporučeno** | 1 |

**Exit 0 znači isporučeno BILO GDJE.** Zato postojeći pozivaoci koji gledaju samo exit kod nisu
slomljeni. Ko treba tačno stanje, čita tekst — ne kod.

## Detalji koji se lako pokvare

- **Transport je Migadu**, ne one.com. Šalje se kroz postojeći `mail-native.js` `sendEmail()`;
  nije pisan nov SMTP klijent. Bilo koja referenca na „one.com" u runbookovima je zastarjela.
- **Idempotency ključ** je obavezan: `sha256(kanal|normalizovana poruka)`, prosljeđuje se kao
  `policyContext.source='system_digest'` da prođe validaciju u `mail-outbox-gateway.js`.
- **Dedup ide u zaseban state fajl** `~/system/state/slack-mail-fallback-dedup.json`. Postojeći
  6-satni Slack keš **nije diran ni oslabljen**. Ključ se markira kao isporučen **tek nakon
  uspješnog slanja** — zato pokvaren mail put pada glasno svaki put, umjesto da utihne poslije
  prvog pokušaja.
- **Fail-closed**: nedostajući `mail-native.js` ili nedostajući `sendEmail` export dižu se u isti
  glasni `DELIVERY_FAILED_BOTH`. Nema tihog no-opa.
- Primalac je `alem@alai.no`, override kroz `SLACK_MAIL_FALLBACK_TO`.

## Kako je dokazano (uživo, ne simulirano)

1. **Slack uspio** — pravi send na `#john-digest` → `✓ Sent to #john-digest`, exit 0, mail se ne dira.
2. **Slack pao → mail stigao** — prisiljeno stvarno nepostojećim kanalom (`resolveChannel()` je
   zaista bacio „Channel not found"). Mail potvrđen **dvaput**: u Sent folderu (uid 104) i
   IMAP pretragom u samom inboxu `alem@alai.no` (uid 3215), s pročitanim tijelom.
3. **Dedup** — isti alert ponovljen odmah → `DEDUPED`, exit 0, drugi mail nije poslan.
4. **Oba pala** — bogus kanal + `SLACK_MAIL_FALLBACK_TO` na domen bez MX zapisa →
   `DELIVERY_FAILED_BOTH`, exit 1.

Nije zasebno paljena samo `require()`-fail grana (brisanje žive produkcijske datoteke je bilo van
opsega); dijeli isti agregacijski put dokazan u testu 4.

Dokazi: `~/system/evidence/107104/` (8 fajlova). Commit `955b8491a5`.

## Odluka o Slack Pro

CEO, 2026-08-18: **ne uzimati Pro za sada.** Greške su dormantne od 13.08., a s fallbackom Slack
ispad više ne uništava podatke nego alerte preusmjeri na mail. Ušteda ≈ 486–594 EUR/god
(€6,75/korisnik/mj godišnje ili €8,25 mjesečno × 6 mjesta). **Ponovo procijeniti ako Slack opet
stane.**

## Poznato otvoreno, namjerno nedirano

- `tldr-weekly-synthesis.js` i dalje radi `process.exit(1)` kad **oba** puta padnu. Zaseban defekt.
- `cmdSend` nikad nije ispisivao `(ts: …)`, pa je regex ekstrakcija `thread_ts` u tom fajlu bila
  **mrtav kod i prije** ove izmjene.
- `bilko-demo-uat-daemon.js`, `email-reactor-digest.js` i `mail-boot-signal.sh` loguju „posted"
  i kad je isporuka išla mailom, jer gledaju samo exit kod. Kozmetika naslijeđena iz njihovog
  vlastitog dizajna.