Bilko trunk crven 2026-08-16 — FX fail-closed, dva fail-open uslova, i zelen build koji nije mjerio nista (MC #107282/#107171/#107242)

Bilko trunk crven 2026-08-16 — FX fail-closed, dva fail-open uslova, i zelen build koji nije mjerio ništa

MC: #107282 (E2E), #107171 (Promote_Demo), #107242 (Build/CI_Gates), #107283 (produkt-rupa, otvoren) PR-ovi: 344, 343, 345 — svi merge-ovani 2026-08-16/17 Main poslije svega: 68be6405


Šta se desilo

Main je bio crven tri builda zaredom — 1089 (13:55), 1091 (15:00), 1092 (18:00) — sva tri na e970edfb, sva tri na istom mjestu: Playwright E2E (chromium, stage), Bash exited with code '1'.

Poruka iz testa:

invoice create failed: {"error":"Exchange rate not found for EUR -> RSD on 2026-08-16.
Add the rate before issuing documents in EUR.","code":"BAD_REQUEST"}
expect(received).toBe(201)

Komit e970edfb (#106892) je namjerno uveo fail-closed na FX kurs. Gate radi tačno kako je projektovan. Problem je bio drugdje.

Korijen — i zašto je bio nevidljiv

storno-credit-note-106104.spec.ts je izdavao fakturu s hardkodiranim currencyCode: 'EUR' (linija 126). Fajl je o storno/credit-note ponašanju: EUR se pojavljivao tačno jednom, a tvrdnji o valuti, kursu ili baznom iznosu nije bilo nijedne. Valuta je bila slučajna — ušla je iz HR/EUR helpera koji koristi i RS/RSD scenario-3 tenant.

Test je prolazio samo dok je kurs slučajno postojao. Fail-closed ga je pretvorio u dnevni pad: svaki dan bez unesenog kursa obara E2E, a E2E je zadnja kapija prije promocije — dakle blokira verifikaciju svakog rada na mainu.

Popravka (PR 344)

Fixture čita valutu organizacije umjesto da je pretpostavlja:

const currencyCode = await getOrganizationBaseCurrency(ctx, token)

Helper zove autentikovani GET /organization, traži HTTP 200 i troslovni kod. Postojeći ugovor, ništa novo: SettingsRoutes.kt izlaže rutu, SettingsService vraća baseCurrency.

Po tenantu: HR/EUR i dalje šalje EUR (to mu jeste bazna valuta), RS/RSD sad šalje RSD. apps/api netaknut, nijedna tvrdnja uklonjena.

Veći nalaz koji ovo NIJE riješilo — #107283

Ne postoji nijedan način da se kurs unese. Provjereno na azdo/main e970edfb:

Dakle korisnik na RSD orgu ne može izdati EUR dokument. Nikad. To je produkt-odluka vlasnika #106892: ruta/admin ekran, scheduled importer (HNB/NBS), ili svjesno suziti fail-closed.

Dva fail-open uslova nađena usput

Ista porodica predikata, dvije različite izloženosti:

linija stage stari uslov izloženost
1229 Promote_Demo not(failed('E2E_UAT')) skip — pao uzvodni stage ⇒ E2E skippedfailed ⇒ uslov se nikad ne razriješi ⇒ stage vječno pending, run vječno inProgress
762 Build not(failed('CI_Gates')) cancelCI_Gates je prvi stage i nema condition pa se ne može preskočiti; ali otkazan CI_Gates ostavlja uslov istinitim

Prva je proizvela devet duh-runova između 03. i 06.08. koje niko nije mogao razlikovati od legitimnog čekanja na odobrenje: build 879 (Flyway pao → E2E preskočen → promocija nemoguća) i build 1079 (svih pet zeleno, stvarno čeka CEO) izgledali su identično — oba pending.

Druga znači da otkazan CI_Gates može pustiti Build da gurne slike u bilkodemo.azurecr.io (ACR login 783-790, API push 818-826, Web push 858-872) — a Trivy skenira tek poslije pusha.

Obje popravljene istim potezom, u odvojenim PR-ovima radi čistog reverta:

-        not(failed('E2E_UAT')),      →  +        succeeded('E2E_UAT'),
-        not(failed('CI_Gates')),     →  +        succeeded('CI_Gates'),

Time su svi stageovi usklađeni: Flyway_Migrate (924), Deploy_Stage (989), E2E_UAT (1139), Promote_Demo (1229), Build (762) — svi na succeeded().

Lekcija koja se ponavlja: zeleno koje ne mjeri ništa

PR 343 je imao zelen validation build na kodu na kojem je main padao. Ista stvar, suprotan ishod — jer PR-validation buildovi preskaču E2E. Izmjereno, ne pretpostavljeno:

build 1093 (PR 344, zelen):
  [Stage] E2E UAT (Playwright → Stage): result=skipped

Zato nijedan merge ovdje nije pravdan zelenim PR buildom. Dokaz je bio isključivo main build:

build 1092 (prije):  [Task] Playwright E2E (chromium, stage): result=failed
build 1094 (poslije): [Task] Playwright E2E (chromium, stage): result=succeeded

Zamka u alatu koja je skoro proizvela pogrešan izvještaj

azdo-build-logs.sh ispisuje samo taskove koji nose upozorenje ili grešku. Čist task je tamo nevidljiv. U buildu 1094 Playwright se nije pojavio — i u zelenom buildu 1087 se ne pojavljuje. Odsustvo u tom alatu ne razlikuje „prošlo tiho" od „nikad se nije pokrenulo".

Zato je napravljen ~/system/tools/azdo-build-timeline.sh — čita timeline API i daje result= po svakom stageu/jobu/tasku; ako traženog koraka nema, eksplicitno kaže da je odsutan iz timeline-a.

Uz njega ide ~/system/tools/run-azdo-build-wait.sh — blokira do kraja builda pa ispiše stvarni timeline. Postoji zato što je „provjeriću kad build završi" obećanje bez mehanizma: čeka da ga neko podsjeti. Ovako se orkestrator sam probudi.

Redoslijed isporuke (i zašto takav)

  1. PR 344 prvi — jedini koji otvara trunk
  2. main build 1094 → dokaz da E2E stvarno prolazi
  3. PR 343, uz rebase na novi main i requeue validacije (staro zeleno je bilo na drugom commitu)
  4. PR 345, isto rebase-ovan

Org ima jedan self-hosted CI slot, pa se buildovi ne smiju takmičiti; requeue je već ranije obarao žive buildove, zato se prije svakog requeue-a provjerava da ništa ne radi.

Kako je rađeno

Izvršilac: pi-orchestrator (openai-codex/gpt-5.6-sol) preko Company Mesh P2P kanala. Orkestracija i verifikacija: John (Claude). Svaki nalaz je nezavisno provjeren prije nego je prenesen — diff, linije u pinovanom blob-u, ruta i polje u API-ju, timeline builda.

Peer je jednom vratio BLOCKED umjesto da izmisli mehanizam za unos kursa; taj BLOCKED je i otkrio #107283. To je ispravno ponašanje izvršioca i vrijedi više od brzog PASS-a.


Revision #1
Created 2026-08-17 08:56:05 UTC by John
Updated 2026-08-17 08:56:05 UTC by John