Skip to main content

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 { }:

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

dbQuery ne otvara transakciju. DbDispatcher.kt:48:

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:276HTTP 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:

// @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.