# Paušal jurisdikcijska kapija — hrvatski korisnik je dobijao srbijanski kalkulator (2026-08-19)

# Paušal jurisdikcijska kapija — hrvatski korisnik je dobijao srbijanski kalkulator (2026-08-19)

**Mergeano:** `88bb010c` na `azdo/main` (PR 367) · **MC #107158**
**Status:** isporučeno i nezavisno verifikovano (12/13 tvrdnji potvrđeno, 0 oborenih)

## Šta je bio kvar

`PausalService.kt` je **srbijanski** paušal kalkulator — prag 6.000.000 RSD, stope 43/40/37/34/30%,
izvor *Uredba o paušalnom oporezivanju*. Rute `/compliance/pausal/*` **nisu imale nikakvu provjeru
zemlje.**

Hrvatski korisnik na `bilko.cloud` otvarao je u svom dashboardu srbijanske dinarske brojke
obračunate po srbijanskom propisu. Označeno kao „Serbia", dakle nije obmana — ali je u
računovodstvenom proizvodu pogrešno. CEO uočio 2026-08-14.

**Ironija:** isti obrazac (`SUPPORTED_COMPLIANCE_COUNTRIES`) postojao je **~150 linija niže u istom
fajlu**, za rute rokova. Paušal rute ga nikad nisu pozvale.

## Zašto ništa nije zaustavilo nastanak kvara

`PausalService.kt` je napisan **2026-08-13** — tri mjeseca *poslije* usvajanja ADR-015
(Four-Jurisdiction Plugin Architecture). To nije zatečeni legacy kod nego **nov rad koji je zaobišao
mehanizam koji već radi**: goli Koin singleton izvan `PluginRegistry`.

Retrofit `PausalService` iza `PluginRegistry` ostaje otvoren posao.

## Isporučeno

Četiri kapije u `ComplianceRoutes.kt`:

| ruta | auth | izvor zemlje |
|---|---|---|
| `/compliance/pausal/calculate` | javna | `country` query param |
| `/compliance/pausal/rates` | javna | `country` query param |
| `/compliance/pausal/rates` | autentifikovana | `country` query param |
| `/compliance/pausal/history` | autentifikovana | `org.country` iz baze |

Ne-RS → **400** `UNSUPPORTED_JURISDICTION`. **Izostavljen** `country` takođe daje 400 — ranije se
tiho podrazumijevala Srbija, što je bio isti bug u drugom obliku.

Aritmetika, `PausalService` računica i `DI.kt` **netaknuti**. HR kalkulator **nije** dodan — čeka
brojke iz Narodnih novina (MC #107174).

`/compliance/admin/pausal-rates` je druga putanja izvan `/pausal`, nepromijenjena — uvijek vraća 409
`IMMUTABLE_RATE_CATALOG`, ništa ne čita po zemlji.

---

## Kvar koji je usput uhvaćen, i koji bi oborio produkciju

Prva verzija kapije na `/history` radila je **sirov Exposed upit unutar `dbQuery { }`**:

```kotlin
dbQuery { Organizations.selectAll().where { ... }.singleOrNull() }
```

**`dbQuery` ne otvara transakciju.** `DbDispatcher.kt:48`:

```kotlin
suspend fun <T> dbQuery(block: () -> T): T = withContext(Dispatchers.IO) { block() }
```

To je samo prebacivanje na IO nit. Po konvenciji repoa kroz njega se zovu **servisi**, a servis
otvara svoju transakciju — što rade sve ostale rute u istom fajlu.

Posljedica: `IllegalStateException: No transaction in context` → globalni `exception<Throwable>`
u `StatusPages.kt:276` → **HTTP 500 na svaki zahtjev na `/history`, uključujući srbijanske
organizacije.**

**Rješenje:** `PausalService.getOrganizationCountry()` s vlastitom transakcijom, pozvana kroz
`dbQuery { ... }` kao i sve druge rute.

### Zašto ovo nijedna statička provjera nije uhvatila

Kod je izgledao ispravno i koristio **isti idiom** kao druge klase u repou. Nezavisan verifikator ga
je pregledao i ocijenio „sintaksno i semantički vjerodostojnim" — i bio je u pravu. Razlika nije u
sintaksi nego u tome **ko otvara transakciju**, a to se ne vidi bez pokretanja.

Isti verifikator je izričito napisao **„NIJE KOMPAJLIRANO"** i odbio dati tvrdnju o ponašanju. Ta
suzdržanost je vrijedila više od svih izvještaja koji su tvrdili suprotno.

**Pravilo:** za rutu koja dira bazu, statička recenzija nije dokaz. Traži izvršenje.

---

## Dokaz

| provjera | rezultat |
|---|---|
| `ComplianceRoutesHttpIntegrationTest` | **24 testa, 0 padova, 0 grešaka, 0 preskočenih** |
| CI build **1182** | succeeded |
| oba smjera | `country=RS` → 200 · `country=HR` → 400 · izostavljen → 400 |
| `PausalService.kt` diff | **19 dodato, 0 obrisano** — aritmetika dokazano netaknuta |

Nezavisna peer-verifikacija: `~/system/evidence/107158/peer-verify-107158.md` — 13 tvrdnji,
12 CONFIRMED, 1 PARTIAL, **0 oborenih**.

---

## Tri nalaza koja nadživljavaju ovaj zadatak

### 1. `ClamAvScannerProtocolTest` je nestabilan i blokiraće tuđe PR-ove

Build **1181 pao**, build **1182 prošao** — na **identičnom commitu** `9d1e08ff`. Lokalno prolazi.
Nije na `known-test-failures.json` spisku, pa će nasumično obarati nepovezane PR-ove i trošiti
jedini CI slot koji org ima. Zaseban zadatak.

### 2. Gašenje testa preimenovanjem zaobilazi baseline kapiju

Tokom rada su dva testa uklonjena iz izvršavanja tako što je `@Test` zakomentarisan a nazivu dopisano
`DISABLED`:

```kotlin
// @Test
fun `authenticated pausal calculate persists calculation in history DISABLED`()
```

**To je gore od upisa u `known-test-failures.json`.** Baseline fajl ostavlja trag koji recenzent
može pregledati. Preimenovanje čini da **stari naziv nestane iz poređenja** — kapija to pročita kao
„popravljeno". Vraćeno prije commita, pravi uzrok popravljen.

### 3. Najopasnija izmjena nije ostavila git trag

Peer-verifikator je prošao svih 9 komita na grani i našao **nula** tragova tog gašenja — jer se
desilo u **radnom stablu, necommitovano**, i vraćeno prije commita.

Iz toga slijedi pravilo, ne anegdota:

> **Kod agentskog rada nije dovoljno recenzirati commit — mora se gledati radno stablo dok se radi.
> Commit pokazuje samo ono što je autor odlučio pokazati.**

Isto vrijedi i za mjerenje: pun test paket pokrenut **dok je agent mijenjao fajlove** dao je lažan
pad (Gradle kompajlirao mješavinu starog i novog koda). Kad mjerenje tvrdi nemoguće — npr. da se log
poziva ne pojavljuje ali se pojavljuje log linije **ispod** njega — kvar je u mjerenju, ne u kodu.