Na čemu ovo radi danas, na čemu bi trebalo raditi sutra, i koja jedna odluka stoji između to dvoje.
Putanje i gate premjereni 18. rujna 2026. Stranica je pisana 16. rujna, dan prije podjele monorepa, pa je govorila o jednom repozitoriju i jednoj naredbi. Dvije tvrdnje su time pale: daljinski repozitorij više nije jedan, i gate više nije npm run check. Oboje je ispravljeno uz naredbu; gdje se brojka promijenila, piše i što je ovdje stajalo. Ono što podjela nije dirnula — nijedan poslužitelj trgovine, nijedan pipeline, nijedna sigurnosna kopija — stoji nepromijenjeno, jer se infrastruktura rezom repozitorija ne gradi.
Jedan poslužitelj postoji, i to onaj koji nadzire zalihe. Trgovina nema svoj — ni produkcijski ni probni. Način posluživanja je odlučen (statični Astro + Vendure API), veličina i cijena su izračunate, ali izbor paketa stoji neodlučen od 20. srpnja i drži roadmap stavku 12.
| Dio | Stanje | Gdje |
|---|---|---|
| Repozitoriji na daljinu | radi Deset ih je od 17. 9., pod grupom gitlab.com:plant-garden/ — devet u podgrupi flora-platform i sync-nexus u ecommerce-tools. Gura se ručno, u naletima. Do 18. 9. ovdje je stajao jedan: rasadnik-milic/nova-produkcija/new-way.git, koji je bio remote monorepa i danas ne pripada nijednom checkoutu u flora-baseu. | git remote get-url origin po repozitoriju |
| Kontrolni poslužitelj (nexus) | uživo od 14. 9. sync-probe.rasadnik-milic.hr, docker compose, isporuka Ansibleom, noćni posao retencije u 3:00. Jedini stroj koji ovaj projekt drži u pogonu. | flora-base/sync-nexus/ansible/ |
| Slika trgovine (Docker) | postoji, ne vrti se flora-core/Dockerfile gradi server, ali je nijedan stroj ne pokreće. | flora-core/Dockerfile bilo ecommerce/Dockerfile |
| Razvojna baza | radi Postgres 16 u flora-core/compose.dev.yml, baza plant_garden na 5432, podaci u imenovanom volumenu. Kontejner se od podjele zove flora-core-postgres-1. | flora-core/compose.dev.yml |
| CI | faze 1 i 2 rade Gate na svaki push, na vlastitom runneru (flora · zajednicki · cx23, GitLab Runner 19.4.0). Predložak je jedan — flora-infra/ci/gate.yml — a sedam repozitorija nosi po desetak redaka koji ga includeaju. Izmjereno na runneru: flora-base 33 testa / 8,7 s, flora-atlas 144 poveznice / 7,5 s, flora-libs 76 / 24,3 s, sync-nexus 650 / 85,6 s, flora-core 3.325 / 436 s. www-plant-garden-hr 75 / 70,4 s i www-plant-garden-si 78 / 68,8 s. faza 2 radi od 19. 9. Slika se gradi Kanikom i nosi dva taga — $CI_COMMIT_SHA i latest — skeneri (hadolint, Trivy nad slikom i nad lockfileom) se vrte zaključani digestom i padaju samo na porast iznad osnovice u repozitoriju, a dev stroj sliku povlači deploy tokenom opsega samo read_registry i pokreće je Quadlet jedinicama koje su ga čekale. Build 880 s, slika 411.751.879 B, 94 migracije → 149 tablica, backup + dokazani restore 11 s. api-dev.plant-garden.hr je Vendure 3.7.3, ne više naša stranica o nedostupnosti. faza 3 radi od 20. 9. Storefront .si se gradi na runneru protiv žive dev Vendure i diže wranglerom na Pages: build:si 246,2 s / 1.712 stranica, pages:si 41,8 s / 1.938 datoteka, deploy 1b32e901 na dev-v2.plant-garden.si. Build je zaseban job, gate je ostao type-check + test bez baze, a tree_clean se vrti i u buildu — bez www-plant-garden-hr u estateu pravni manifest padne s 8 na 3 unosa, i straža to hvata. Faze 4–6 čekaju. do 20. 9. ovdje je stajalo „Faze 3–6 čekaju"; 18. 9. ujutro „ne postoji"; ./flora check i dalje radi isto lokalno | roadmap 3 · nalaz, poglavlja 11 i 12 |
| Probna okolina | postoji za SI dev-v2.plant-garden.si je živ — curl 25. 9. vraća HTTP 200, s API-jem na flora-dev. Do 25. 9. je ovdje pisalo „ne postoji", što je prestalo biti točno kad je faza 3 objavila SI trgovinu. Ostaje otvoreno za HR i za prelazak na produkciju: ovo je mjesto na kojem se gradi i objavljuje, ne još i ono na kojem se prelazak uvježbava s podacima. | roadmap 7 i 31 |
| Isporuka storefronta | radi za .si od 20. 9. Naš runner gradi, wrangler diže na Cloudflare Pages, bez Workera. Pipeline #2865211959: build:si 246,2 s → 1.712 stranica, pages:si 41,8 s → 1.938 datoteka na projekt flora-dev-si. Stranica je dev-v2.plant-garden.si i vraća 200. .hr se i dalje ne objavljuje — flora-dev-hr nema ni domenu ni ijedan deploy, i ostaje samo susjed u estateu da pravni manifest ispadne cijel. do 20. 9. ovdje je stajalo „ne postoji — odlučeno je kako, ali nijedan build nije objavljen"; provjereno nezavisno u izvještaju 10b | roadmap 40 · 10a, 10b |
| Sigurnosna kopija i nadzor | ne postoji za trgovinu. Lokalne kopije postoje samo za nexus i WP plugin, i to kao nusprodukt Ansible isporuke. | roadmap 44 i 45 |
Tri paketa su izračunata na pravoj veličini kataloga (1.545 proizvoda, 168.332 prodane jedinice kroz život trgovine). Traženi najgori slučaj nedostupnosti je ≤ 1 sat. Preporuka dokumenta je paket B.
| Paket | Cijena/mj. | Ispunjava ≤ 1 h | Gubitak podataka | Teret održavanja |
|---|---|---|---|---|
| A — jedan stroj | ≈ €10–15 | ✗ (2–4 h) | ≤ 24 h | najmanji |
| B — app i baza odvojeno, PITR preporuka | ≈ €20–30 | ✓ (30–60 min) | minute | umjeren |
| C — B plus topla pričuva | ≈ €50–70 | ✓✓ (5–15 min) | sekunde | najveći |
Sva tri ostaju daleko ispod današnjih ≈ €120/mj. za WordPress, čak i dok oba sustava rade usporedno.
Redoslijed rada, vlasnikova odluka 19. rujna 2026. Nadglasava svaki drugi poredak. Prvih pet koraka su put od pusha do stranice koju netko može otvoriti — ne točan sadržaj na kraju tog puta.
Tek iza toga: nexus, nadzor nad svime, pravi produkcijski katalog, frontend, CMS.
Backup i restore nisu u ovome i nisu propust. Pripadaju nexusu. Brana pred migracijom izvađena je iz flora-deploya 19. 9. i skinuta sa stroja, jer je stajala ispred pohrane koje nema — dug, ne otkaz: vraća se s fazom koja donese stvarnu pohranu, a tekst odbijanja čeka u komentaru koji ju je zamijenio.
Isporuka postoji, ali je sva ručna. Gate se pokreće rukom, nexus se deploya rukom, kod se gura rukom i u naletima. Ništa od toga nije pokvareno — samo ovisi o tome da se netko sjeti.
Redoslijed je ovisnost, ne želja. Tablica je zadržana iz verzije od 16. rujna i ažurirana 18. 9. — gdje se brojka ili naredba promijenila, piše što je ovdje stajalo.
| Faza | Što radi | Kad je gotova | Ovisi o |
|---|---|---|---|
| 1 · Gate na svaki push izvedeno 18. 9. 2026. | Na svaki push pokreće gate svakog repozitorija njegovom naredbom i pada kad gate padne. Bez deploya, bez artefakta, bez ijedne tajne — nula variables: stavki, a jedina vjerodajnica je ugrađeni CI_JOB_TOKEN. Testova na runneru u sedam repozitorija: node flora-atlas/measure-ci.mjs (19. 9.: 4.433). Ovdje je brojka zastarjela tri puta u dva dana — 16. 9. „pokreće npm run check", 18. 9. ujutro „4 204 testa u šest gateova", pa 4.237, pa 4.377. Zato je sada naredba, a ne broj: čita se iz zadnjeg zelenog pipelinea po repozitoriju |
dokazano Crveni push: pipeline #2862605195, izlazni kod 1, × is the versionLabel with a .pdf suffix. Zeleni: #2862589621, #2862589705, #2862593464, #2862589894, #2862589797 — svi exit 0. | runner odlučeno: vlastiti |
| 2 · Gradnja slike izvedeno 19. 9. 2026. | Gradi flora-core/Dockerfile Kanikom — bez Docker socketa i bez privilegija — i sprema sliku pod oznakom commita. Pet skenera zaključanih digestom, latest se miče tek iza gatea, pa deploy na dev. 8 uspješnih gradnji, 13 pipelinea, svi zeleni. stageovi su test → prepare → build → scan → deploy; build:image teče usporedo s gateom, pa se njegovih 364 s ne zbraja |
dokazano Slika se povlači i vrti na dev stroju; api-dev.plant-garden.hr odgovara Shop API-jem. Migracije imaju branu pred sobom. | 1 |
| 3 · Gradnja storefronta nije počelo | Jedan build po domeni, pa wrangler na Cloudflare Pages. Ne pokreće ga push nego trigger iz backenda na izmjenu sadržaja. Build ima ogradu: pada ako katalog ispadne premalen. | Objavljeni sajt ima 1.650 (HR) odnosno 1.637 (SI) URL-ova u sitemapu i ispravan robots.txt. | 1, odluka o hostingu |
| 4 · Probna okolina nije počelo | Isti artefakti idu na staging prije produkcije. Baza mu se puni restoreom zadnjeg verificiranog backupa, anonimizirano — ne novim serverom s dijelom podataka. | Prolaz kroz naplatu na stagingu, s pravim mailovima u testni sandučić. | 2, 3, poslužitelj |
| 5 · Produkcija na dugme nije počelo | Isporuka iste slike koja je prošla staging, s migracijama i mogućnošću povratka. Backup se prije migracija automatski testira restoreom. | Povratak na prethodnu verziju izmjeren u minutama, ne u nagađanju. Taj broj još ne postoji. | 4, odluka o paketu |
| 6 · Nexus u isti lanac postoji ručno | Ansible koji danas radi rukom pokreće se iz pipelinea, s istim zapisom ishoda. | Deploy ostavlja trag koji preživi računalo — danas je ansible.log jedini zapis, i u .gitignore je. | 1 |
| Pitanje | Odgovor | Brojka iza njega |
|---|---|---|
| Runner — dijeljeni ili vlastiti | vlastiti | gate 3 min 35 s slijedno, 1 min 06 s uz paralelizam; hladan npm ci u jezgri 33,1 s / 163 MB |
| Gdje žive tajne | GitLab CI/CD variables, masked i protected | danas 0 tajni u CI-ju; 33 ključa u flora-coreu, po 5 u svakom storefrontu, svi samo na računalu — puni popis je u odjeljku Varijable. Faza 1 ne treba nijednu |
| Tko pokreće produkcijski deploy | GitLab Environments, deploy je manual job | danas jedna osoba i jedan stroj: 96 Ansible prolaza u četiri dana, bez traga koji preživi računalo |
| Migracije — automatski? | da, uz backup koji se prije toga automatski uzme i automatski testira restoreom | 94 migracije; sve imaju down(), ali 6 ih je namjerno prazno — testirani backup je jedino što ih pokriva |
Otvorena je ostala jedna, i ne blokira fazu 1: kako @flora/client putuje između repozitorija. Prijedlog i pokus koji ga nosi su u nalazu, poglavlje 6.
Odavde nadalje je nacrt pipelinea, izmjeren 18. rujna 2026. Kartica je do 18. 9. nosila samo skicu faza i četiri otvorena pitanja. Faze su sada razrađene po repozitoriju i po git događaju, a četiri pitanja su dobila odgovore — stoje niže, u odjeljku o granama, bazi i Free tieru. Sve brojke su izlaz naredbe u izoliranim klonovima s npm ci iz nule; puni nalaz je u ci-nalaz-2026-09-18.md.
Faze 1 i 2 postoje i vrte se; faze 3–6 su i dalje nacrt. Faza 2 je izvedena u noći 18./19. 9. — gradnja slike, skeneri, prvi deploy na dev i backup uz njega; njezina su mjerenja u odjeljku „Faza 2, kako je ispala", a četiri ispravka nalaza i dvije greške koje je usput otkrila u njegovom poglavlju 12.
Odjeljak odmah ispod je ono što je 18. 9. navečer izvedeno i izmjereno na vlastitom runneru — s ID-evima pipelinea. Sve što dolazi poslije odjeljka „Odakle se kreće" je nacrt, mjeren istog dana u izoliranim klonovima s npm ci iz nule; gdje nešto stoji kao odluka, odluka je donesena i zapisana u nalazu, a gdje stoji kao prijedlog, čeka izvedbu da ga obori ili potvrdi. Tri mjesta na kojima je izvedba nalaz ispravila stoje u njegovom poglavlju 11.
Gate se vrti na vlastitom runneru na svaki push. Zajedničkog predloška više nema — flora-infra/ci/ je ukinut 25. 9., a svaki repozitorij drži cijeli svoj pipeline u vlastitoj mapi .ci/, jedna datoteka po fazi (deps, build, test, image, verify, release), a .gitlab-ci.yml ih samo uključuje redom. Do 25. 9. je ovdje pisalo suprotno: jedan predložak u flora-infra/ci/gate.yml koji svi includeaju — takav je bio od 18. 9. sync-nexus je i dalje sam, jer s Node jobovima ne dijeli ništa.
| Repozitorij | Naredba | Testova | Job | Kod | Mjerilo od 18. 9. ujutro |
|---|---|---|---|---|---|
| flora-base | node tests/run.mjs | 33 | 8,7 s | 0 | nije bio u tablici; 3,1 s lokalno |
| flora-atlas | node check-links.mjs | 144 poveznice | 7,5 s | 0 | 113, fusnota 117 — repozitorij je istog dana narastao |
| flora-libs | npm ci && npm run check | 76 | 24,3 s | 0 | 76 · 2,9 + 1,4 s |
| sync-nexus | php tests/run.php | 650 | 85,6 s | 0 | 650 · 21,2 s |
| flora-core | check && test && build:dashboard | 3.325 | 436,4 s | 0 | 3.325 · 18,1 + 64,4 s mjereno 18. 9.; nakon 11a je 3.531 testa u 268 s — vitest 62,9 s, build:dashboard 99 s |
| www-plant-garden-hr | type-check && test | 75 | 70,4 s | 0 | 75 · 7,7 s |
| www-plant-garden-si | type-check && test | 78 | 68,8 s | 0 | 78 · 8,1 s |
Sve je izlaz jobа na flora · zajednicki · cx23, GitLab Runner 19.4.0, slika node:24.16.0-bookworm zaključana digestom. Trgovine su pushane 18. 9. navečer — do tada je njihov pipeline stajao napisan i nedokazan. Pipelinei: #2862623583 (HR) i #2862623724 (SI).
Slika se gradi Kanikom, bez Docker socketa i bez privilegija — Buildah na ovom runneru ne radi, mjereno. Pet skenera je zaključano digestom, a latest se pomiče tek iza gatea, pa se nikad ne objavi nešto što gate nije vidio.
| Job | Stage | Trajanje |
|---|---|---|
| gate | test | 491 s |
| legal-assets | prepare | 26 s |
| build:image | build | 364 s usporedo s gateom |
| tag:latest | scan | 19 s |
| pet skenera | scan | 9 · 27 · 15 · 16 · 14 s |
| deploy:dev | deploy | 122 s |
Kritični put 632 s, zbroj svih jobova 1 103 s; bez gradnje 392 s. Gate je 78 % kritičnog puta i jedina poluga koja bi to bitno skratila je brži runner — build:image se ne zbraja jer teče usporedo.
Tablica gore je zapis faze 1 i ostaje kakva jest. Ovo je ono što je došlo poslije: sesija prompta 41 spojila je storage connector u main obaju repozitorija, i gate je prvi put provjerio posao koji nije vidio dok je nastajao.
| Repozitorij | Pipeline | Testova | Trajanje | Kod |
|---|---|---|---|---|
| flora-libs | #2862639896 | 76 | 21,9 s | 0 |
| flora-core | #2862642729 | 3.465 u 340 datoteka bilo 3.325 u 325 | 382,6 s | 0 |
Storage connector je donio 140 novih testova i svi prolaze u čistom kontejneru s punim estateom. Gate je usput uzeo flora-libs s novom permisijom ManageStorageConnector — ta dva repozitorija razvijala su se na odvojenim granama i ovo ih je prvi put išta provjerilo zajedno. Ukupno je tada bilo 4.377 testova u sedam gateova; današnji zbroj daje node flora-atlas/measure-ci.mjs.
Vlasnikova odluka od 18. 9. dijeli gate trgovina: type-check + test na svaki push, build na trigger iz backenda. Brojka iza odluke bila je 15,8 s za obje trgovine, mjerena na M1 Pro. Na runneru je, s oba jobа u isto vrijeme na 2 vCPU:
| Korak | HR | SI |
|---|---|---|
| sastavljanje estatea — 4 klona | 11 s | 11 s |
| npm ci u flora-libsu | 5 s | 5 s |
| npm ci vlastiti | 17 s | 15 s |
| type-check | 24 s | 24 s |
| test | 4 s | 5 s |
| tree_clean | < 1 s | < 1 s |
Sam gate je 28 s odnosno 29 s — 3,6× naspram mjerila. Odluku to ne mijenja: razlog za podjelu nije bio broj sekundi nego to što astro build traži živu i napunjenu bazu. Obje trgovine zajedno i dalje stanu ispod minute, a build bi im dodao 112 s i jednu bazu. Brojka se ovdje ipak zapisuje, jer 15,8 s više nije istina na stroju na kojem se to vrti.
Najvažniji redak nije brojka nego tree_clean: u oba jobа flora-core je izašao čist. Gate trgovine upisuje pravni manifest u tuđe stablo, i to je smjer koji u poglavlju 4.4 obara 8 unosa na 3. S punim estateom manifest se vraća bajt po bajt isti.
Ista funkcija tree_clean koju vrti pipeline, puštena nad estateom kojem je maknut jedan storefront:
legal manifest: 5 immutable document(s) — hr: 5
tree-clean
DIRTY flora-core
M src/plugins/legal-checkout/generated-legal-document-manifest.ts
.../generated-legal-document-manifest.ts | 33 ----------------------
1 file changed, 33 deletions(-) exit 1
S oba storefronta na mjestu: 8 dokumenata, sva četiri stabla čista, exit 0.
Raspored nije izabran nego iznuđen: file:../flora-libs/… i --layout ../legal-layout.json traže susjeda točno jednu razinu gore. GitLab projekt već checkouta u /builds/<namespace>/<project> — pa je roditelj taj korijen, a repozitorij pod testom je već na mjestu. flora-base ide u taj roditelj; njegov .gitignore već ignorira svaki susjedni repozitorij po imenu, pa korijen ostaje čist za stražu. Ništa se ne premješta i ništa ne kopira.
Jedina vjerodajnica je CI_JOB_TOKEN, koji GitLab izdaje po jobu i poništava kad job završi. Traži da svaki susjed pusti ovaj projekt u CI/CD job token allowlist — to je postavka u GitLabu, ne u kodu, i postavljena je usko: točno ono što je izmjereno, nijedan unos više.
$ grep -n '^[[:space:]]*variables:' <svih 8 datoteka> (no match) $ grep -oh 'CI_[A-Z_]*' <svih 8> | sort -u CI_COMMIT_REF_NAME CI_JOB_TOKEN CI_PROJECT_DIR CI_PROJECT_NAME CI_PROJECT_NAMESPACE CI_SERVER_HOST $ find . -maxdepth 2 -name .npmrc (nema nijedne)
Svih šest su ugrađene GitLabove varijable. Jedine ostale ljuskine varijable su ESTATE, koju skripta sama postavlja, i PWD.
Slika se gradi Kanikom, skeneri se vrte nad njom, i dev stroj je pokreće jedinicama koje su ga čekale od faze 1. Predložak je flora-infra/ci/image.yml i uključuje ga samo flora-core — jedini repozitorij koji ima sliku. Faza 1 je nedirnuta i i dalje zelena u svih sedam repozitorija.
| Korak | Job | Što radi | Prije | Sada | Kod |
|---|---|---|---|---|---|
| test | gate | faza 1, nedirnuta | 403 s | 491 s | 0 |
| prepare | legal-assets | estate, pa 8 pravnih PDF-ova (hr 5, si 3) kao artifact | 27 s | 26 s | 0 |
| build | build:image | Kaniko → $CI_COMMIT_SHA | 880 s | 363 s | 0 |
| scan | tag:latest | crane pomakne latest — tek iza gatea | — | 19 s | 0 |
| scan | scan:dockerfile | hadolint | 10 s | 8 s | 0 |
| scan | scan:image | Trivy nad gotovom slikom, iz registryja | 49 s | 26 s | 0 |
| scan | scan:deps | Trivy nad package-lock.json | 26 s | 14 s | 0 |
| scan | scan:secrets | gitleaks nad radnim stablom — 0 nalaza | — | 16 s | 0 |
| scan | scan:baseline | usporedba s osnovicom — jedini job koji smije pocrvenjeti | 11 s | 13 s | 0 |
| deploy | deploy:dev | ssh deploy@dev → flora-deploy | 13,4 s | 110 s | 0 |
| Pipeline ukupno | 1.350 s | 633 s | 0 | ||
Ovo je prestalo vrijediti 25. rujna. Pravni dokumenti su prešli u Vendure — uređuju se u Dashboardu, izlaze kroz Shop API, i više se ne peku u PDF pri buildu. S njima su nestali flora-base/legal-layout.json, skripte legal:assets / legal:manifest / legal:pdfs, mapa flora-core/static/legal i manifest API u legal-tooling. Posljedica za estate: flora-core više ne treba oba storefronta pokraj sebe da bi se sagradio, a build trgovine bez pravnih dokumenata prolazi uz upozorenje umjesto da padne. Odjeljak ispod ostaje kao zapis kako je bilo dok je vrijedilo — i zašto je bilo tako.
Svjež klon flora-corea nije se mogao sagraditi (do 25. 9.). Osam pravnih PDF-ova generirao je legal:assets, koji čita legal-layout.json iz flora-basea, alat iz flora-libsa i same dokumente iz oba storefronta. Dockerfile odbija graditi bez njih namjerno: potvrda narudžbe bez zakonski obaveznog priloga gora je od neposlane.
$ git ls-files static/legal | wc -l 0 $ legal-assets job: legal pdfs → hr: 5 rendered, si: 3 rendered $ tree-clean: clean flora-platform · flora-core · flora-libs · www-plant-garden-hr · www-plant-garden-si
Zato je legal-assets zaseban Node job koji PDF-ove preda Kaniku kao artifact: Kanikova slika nema Node, a estate se u njoj ne da sastaviti.
ci/scan-baseline.json u repozitoriju pod testom je način na koji se prethodni prolaz pamti — ne job, ne artifact s retencijom, ne varijabla, nego datoteka koja ulazi u diff. Tri skenera samo pišu nalaz i prolaze; scan:baseline je jedini koji smije pocrvenjeti, i pada samo na porast.
Pad ispod osnovice nije greška nego poruka koja traži da se osnovica spusti istim commitom koji ju je zaslužio. Dokazano u oba smjera prije nego je job ikad pušten: netaknuta osnovica → GREEN, exit 0; osnovica spuštena za jedan HIGH → HIGH 7 -> 8 WORSE +1, RED, exit 1.
Stvoren 18. 9., na projektu flora-core, korisnik flora-dev-pull, bez isteka. Živi u vaultu (ansible/group_vars/dev/vault.yml), ne u GitLab CI/CD varijablama — jer završava na stroju, u ~deploy/.config/containers/auth.json. Pravilo je staro: tajna koja završi na stroju ide u vault, tajna koja upravlja infrastrukturom ide u .env.
Koliko je uzak — istim tokenom protiv četvero vrata:
GET /api/v4/projects/86591547 404 GET /api/v4/projects/86591547/repository/files/… 404 git ls-remote flora-core 403 You are not allowed to download code. GET /jwt/auth?service=container_registry&…:pull token izdan
Migracije se vrte unutar kontejnera, pri startu: flora-core/src/index.ts zove runMigrations(config) prije bootstrap(config). Zato ne postoji korak pipelinea „prije migracije" — postoji samo trenutak prije nego slika smije da se promijeni, i on je u flora-deployu.
podman pull <slika> bajtovi na disk; ništa se ne pokreće
flora-backup dump → restore u drugi, prazan Postgres
→ prebroji tablice i retke s obje strane
pao? DEPLOY REFUSED, ništa nije restartano
podman auto-update ono što jedinice deklariraju
systemctl --user restart i tek sada išta radi
Backup nije uvjetovan time nosi li build novu migraciju — restart je migracijski prozor svejedno. Iznimka odluke 8 (prva migracija u praznu bazu) odlučuje se brojanjem tablica, ne pamćenjem da je prvi put, pa se ne može tiho proširiti.
| Slika | Vrijednost |
|---|---|
| Digest | node flora-atlas/measure-ci.mjs — vezano uz zadnji zeleni pipeline, ne uz upisanu vrijednost |
| Tagovi | ispisuje ih ista naredba, i to onakve kakvi u registryju jesu u trenutku pitanja |
| Veličina u registryju | ista naredba; prolaz 19. 9. 2026: 411.653.250 B |
| Od 880 s builda, push je | 532 s |
| Migracija pri prvom startu | 94, i 149 tablica iza njih |
| Backup + dokazani restore | 1 s + 8 s, 11,0 s ukupno; dump 492.770 B |
| CVE u slici | 299 — 5 CRITICAL, 88 HIGH, 127 MEDIUM, 76 LOW, 3 UNKNOWN |
| CVE u lockfileu | 16 — 8 HIGH, 6 MEDIUM, 2 LOW (nepromijenjeno od 18. 9.) |
| hadolint | 3 — 2 warning DL4006 (25, 46), 1 info SC2016 (25) |
Kod slike je gdje CVE-ovi žive važnije od zbroja: 228 u paketima Debiana 12.15 iz node:24-bookworm-slim, 46 u ugrađenoj esbuild Go binariji, 25 u Node paketima. Skoro ništa od toga nije naše, i gasi ga podizanje bazne slike — ne pipeline. Šesnaest iz nalaza 9c bio je lockfile, i on je i dalje 16; razlika nije regresija nego druga meta.
Dev nginx na tu stranicu vraća 200, jer Cloudflare odbacuje tijela 5xx — pa dokaz nije kod nego sadržaj.
| Prije | Poslije | |
|---|---|---|
| GET / | 200, 1.538 B, <title>flora-dev</title> | 404, 93 B — Vendureov, ne naš |
| POST /shop-api | 200, ista naša stranica | {"data":{"activeChannel":{"code":"__default_channel__","defaultLanguageCode":"hr"}}} |
| GET /health | 200, ista naša stranica | {"status":"ok"} |
U dnevniku kontejnera: Vendure server (v3.7.3) now running on port 3000.
Ni api.container ni worker.container nisu imali nijedan Volume=, a AssetServerPlugin piše u /app/static/assets unutar kontejnera (vendure-config.ts:92). Sve zapisano ondje živjelo je u zapisivom sloju kontejnera i nestalo bi s prvim podman auto-update: 444 MB u 8.655 datoteka, bez ijedne poruke — jer ništa u stacku nije tvrdilo da ih čuva. Isti oblik kvara kao roadmap 41, samo jednu razinu niže: ondje ga radi Dockerfile koji slike peče u image, ovdje nedostatak volumena. Popravljeno u flora-infra ad0ed8e.
Uz to, isti dan: zamjenska stranica nginxa nadživjela je kvar jer se keširala — posjetitelj je i nakon oporavka gledao poruku o nedostupnosti. Sada se ne kešira (c126fba).
Obje su na dev stroju, i obje su bile nevidljive dok se ništa na tom stroju nikad nije spojilo na bazu. Faza 2 je prvo što jest.
1. Ansible je uzimao $PGDATA natrag od Postgresa. Task „Directory for the database data" je na svakom prolazu tvrdio owner: deploy, mode: 0750; entrypoint slike chowna $PGDATA na svog korisnika (uid 70 unutra, 100069 na hostu kroz userns) i chmodne 0700. Sljedeći ./run site podman to vrati.
$ stat -c "mode=%a owner=%u:%g ctime=%z" /mnt/pgdata/postgres mode=750 owner=1001:1001 ctime=2026-09-18 20:57:03 psql: FATAL: could not open file "global/pg_filenode.map": Permission denied
Postmaster je nastavio raditi, jer je svoje datoteke otvorio prije promjene. Direktorij se sada stvara jednom, s 0700, i više se ne dira.
2. pg_isready je jedanaest sati govorio da je sve u redu. On FATAL odbijanje broji kao „accepting connections", pa je kontejner bio healthy nad bazom koja nije mogla odgovoriti ni na jedan upit. HealthCmd je sada pravi upit. To je ista vrsta tihog zelenila protiv koje u fazi 1 stoji tree_clean — samo što je ovdje trajala jedanaest sati, jer je nitko nije imao čime opovrgnuti.
Iz istog uzroka i jedna manja: DB_HOST u stack.env je pisao postgres, a na mreži flora se razrješava samo flora-postgres (getent hosts postgres → rc=2). Ni taj redak nikad nije bio isproban.
rules:changes na pet jobova koji diraju sliku — legal-assets, build:image, tag:latest, scan:image, deploy:dev. Commit koji ne dira nijednu aplikacijsku datoteku ih uopće ne stvori: gate i tri skenera se i dalje vrte, slika se ne gradi, dev stack se ne restarta.
Dokazano istim pushem koji je pravilo uveo (#2863584314, dira samo ci/**): 392 s, pet jobova nije ni postojalo.
Popis je pozitivan namjerno — imenuje što može promijeniti ono što slika radi. Zaboravljena putanja znači da se prava promjena tiho ne deploya, pa je velikodušan; .gitlab-ci.yml je u njemu da se promjena pipelinea i dalje testira pokretanjem. Vani ostaju samo README.md, ci/**, compose.dev.yml, .gitignore i .env.example.
scan:baseline zato mora razlikovati „skener nije ni pitan" od „skener je pao", i te se dvije stvari ne smiju pomiješati. Razlikuje ih po tome postoji li FLORA_IMAGE_DIGEST iz dotenva: nema slike → ispiše zašto i prođe; ima slike a nema izvještaja → crveno.
Svaki deploy ostavi prethodnu sliku neoznačenom. Izmjereno 19. 9. nakon jednog dana rada:
Images 8 ACTIVE 2 7,779 GB RECLAIMABLE 6,48 GB (83 %) Volumes 8 ACTIVE 1 464,1 MB RECLAIMABLE 391,1 MB (84 %) disk: 5,3 GB -> 14 GB od 38
Još dvadesetak deployeva i disk bi bio pun. flora-deploy sada na kraju radi podman image prune i volume prune — miče samo ono na što ne pokazuje ni tag ni kontejner, pa su pokrenuta slika i Postgres netaknuti. Rollback ih ovdje ne treba: registry čuva sha tagove.
Volumeni su bili moje curenje: flora-backup je radio podman rm bez -v, a postgres slika deklarira VOLUME — pa je svaka provjera restorea ostavljala anonimni volumen.
Nakon čišćenja: 2 slike, 1 volumen, 6,6 GB, kontejneri rade.
Zatečena politika je bila GitLabova zadana i nije štitila ništa: keep_n 5, older_than 90d, name_regex_keep: null — dakle ni latest nije bio izuzet, nego je preživljavao slučajno, zato što je uvijek najnoviji.
sada: keep_n 25 · older_than 30d · name_regex_keep "latest" · cadence 1d
Brisanje je konjunktivno — tag ide samo ako je stariji od 30 dana i nije među 25 najnovijih i nije latest. To bi trebao biti udoban rollback prozor.
Otvoreno pitanje, i ne smije se prešutjeti: od osam sha tagova koje je ovaj dan izgradio, u registryju su ostala samo posljednja dva; ostalih šest vraća 404. Politika ih nije obrisala — cleanup_policy_started_at je null, dakle nikad nije ni trčala. Uzrok nije utvrđen. Tri probna taga postavljena 19. 9. u 08:28 preživjela su deset minuta netaknuta, pa tagove ne briše nikakav kratki ciklus — što uzrok sužava, ali ga ne objašnjava.
U registryju zato stoji retention-canary-2026-09-19, tag koji ne pokazuje ni na što novo i ničemu ne služi osim da se vidi je li još tamo. Ako nestane, rollback prozor ne postoji — i to je provjera koju verifikator može napraviti jednim pozivom. Dok se ne utvrdi uzrok, prozor od 25 tagova je namjera, ne činjenica, i tako ga treba čitati.
legal-assets je dobio needs: [] da ne čeka test stage. Job s needs zaobilazi redoslijed stageova — pa od tog trenutka u fazi 2 ništa više nije ovisilo o gateu: ni build, ni skeneri, ni deploy. Prolaz #2863548867 je ostao siguran samo zato što je concurrent = 2 slučajno odgodio deploy dok gate nije završio. Sreća, ne oblik.
Popravak: build:image sada pusha samo $CI_COMMIT_SHA. Pomični latest, koji dev stroj prati preko AutoUpdate=registry, pomiče zaseban job tag:latest koji čeka gate. crane mu treba 19 s jer mice manifest a ne slojeve — blobovi su već gore. deploy:dev sada izrijekom traži gate.
Tako build i dalje teče usporedno s gateom, a nedokazan commit ne može objaviti tag koji dev stroj slijedi.
Kritični put prolaza #2863559393 (633 s), izmjeren iz vremena jobova:
gate 491 s ████████████████████████ 78 %
tag:latest 19 s █
deploy:dev 110 s █████
ostalo 13 s
─────
633 s
build:image 363 s zavrsio 100 s PRIJE gatea — nije na kritičnom putu
Build se više ne broji. Teče usporedno s gateom i završi prije njega, pa bilo kakvo daljnje ubrzanje gradnje — uključujući vlastiti registry — danas ne skraćuje pipeline ni za sekundu. Poluga je gate.
Unutar deploy:dev (110 s), izmjereno iz vremena u dnevniku:
podman pull 82 s ← 411 MB s GitLaba na dev, ~5 MB/s backup + restore 13 s restart + start 15 s
| Mjera | Očekivano | Cijena | Kad |
|---|---|---|---|
| Veći runner — CX23 (2 vCPU) → CPX31 (4) ili CPX41 (8) | gate 491 s → ~250 s; pipeline → ~400 s | +8 do +19 €/mj, jedan redak u tofu | prvo |
| Ne gradi kad se kod nije mijenjao — rules:changes | commit koji dira samo dokumentaciju ili CI: ~500 s, bez builda i deploya | besplatno | prvo |
| Vitest u krišketima — parallel + --shard | gate → ~200 s, ali samo uz veći runner | besplatno | uz prvo |
| Vlastiti registry na runneru | push ~0 (ne pomaže, build nije na putu); pull na dev 82 s → ~8 s preko Hetznerove interne mreže | servis koji treba čuvati, čistiti i osigurati; gubi se GitLabov UI i deploy token | tek kad gate padne ispod builda |
| Manja slika — 411 MB je node_modules | linearno na push i na pull; ne na kritični put | posao nad ovisnostima, ne nad CI-jem | kasnije |
Ograda na prvi redak: vitest je u nalazu sam prijavio import 180,43 s, tests 15,99 s od 262 s — dakle učitavanje modula, ne izvođenje testova. Više jezgri zato pomaže manje nego linearno. Brojku treba izmjeriti, ne pretpostaviti.
| Prolaz | build:image | Pipeline |
|---|---|---|
| bez cachea | 795 s | 1.350 s |
| punjenje cachea | 1.827 s | 2.006 s |
| cache hit | 372 s | 618 s |
| potvrda, drugi cache hit | 364 s | 622 s |
| latest iza gatea + gitleaks | 363 s | 633 s |
Razlog nije npm ci. Izmjereno na runneru dok Kaniko pusha:
upload runner -> GitLab registry: 361 KB/s download dev <- GitLab registry: ~5 MB/s load average na runneru: 0,19 <- procesor ne radi nista
Dakle ni kompresija ni CPU, nego propusnost prema registryju; jedan sloj je uzeo 508 s. Bez cachea Kaniko svaki put proizvede novi digest za isti sadržaj, pa registry ne prepozna blob i svih 400 MB ide gore iznova. S cacheom se sloj vraća s istim digestom i upload se preskoči.
Za svaku daljnju optimizaciju: jedina preostala poluga je broj bajtova, ne brzina stroja. Manja slika je jedino što je ostalo, i to je posao nad node_modules, ne nad CI-jem.
Uz cache su dodani i gitleaks (četvrti skener, digest iz 9c, 13–15 s, 0 nalaza) i interruptible: true na buildu i skenerima, pa stariji pipeline pusti runner umjesto da se natječe.
Pipeline #2863446599, success, 1.342 s, ovaj put s deploy:dev (102 s) u sebi. Backup nad punom bazom, iz CI-ja a ne iz ruke: 149 tables, 143 rows, dump 492.950 B u 1 s, restore i prebrojavanje u 9 s. Ime dumpa nosi verziju — <pipeline id>-<short sha> — kako odluka 4 traži.
Prompt je tražio da se vrati odluka ako se pokaže da read_registry token nije dovoljan da podman auto-update osvježi sliku bez ljudske ruke. Ne treba je vraćati:
UNIT CONTAINER POLICY UPDATED api.service 4cb0dd515d1c (flora-api) registry true worker.service a1dfe79e7b00 (flora-worker) registry true
Prvi deploy je morao ići kroz eksplicitni podman pull jer kontejnera nije bilo; drugi je išao točno onim mehanizmom koji jedinice deklariraju.
I jedna zamka praga. U istom prolazu je osnovica slike prvi put doista uspoređena — i pala: 299 → 291, uz istu sliku. Trivy je osvježio svoju bazu ranjivosti. Spuštanje osnovice na 291 obojilo bi sljedeći prolaz crveno čim se ta tri HIGH vrate, pa je ostavljena na 299. Pravilo „spusti osnovicu istim commitom koji ju je zaslužio" vrijedi za našu promjenu, ne za drift tuđe baze — i tu razliku scan-compare.mjs danas ne zna napraviti sam.
Prvi deploy je prošao bez backupa i rekao zašto: the database is EMPTY (0 tables in public). Iznimka se odlučuje brojanjem tablica, ne pamćenjem da je prvi put — pa se ne može tiho proširiti.
Nad punom bazom, s namjerno onemogućenim dumpom:
$ chmod 500 /var/backups/flora $ flora-deploy live: 149 tables, 140 rows pg_dump failed. No dump, so no deploy. DEPLOY REFUSED: there is no proven backup. Nothing was restarted. The running version is untouched. exit 1
Vrijeme starta flora-api je prije i poslije identično do nanosekunde. Brana je uz to jednom radila i slučajno, u pravom jobu, prije nego su gornje dvije greške nađene — deploy je stao jer se backup nije dao dokazati.
Faza 1 je gate na svaki push — bez deploya i bez ijedne tajne. Kopiraj Developer u novu sesiju. Kad ona javi da je gotova, kopiraj Verifikator u drugu sesiju: ona ništa ne popravlja, nego provjerava. Format je kućni, iz prompts/README.md.
Predradnje — bez ovoga sesija stane na prvoj prepreci:
1. Vlastiti runner mora biti registriran na grupi plant-garden i uzimati poslove. Odluka je donesena, registracija nije provjerena.
2. CI_JOB_TOKEN svakog repozitorija mora smjeti čitati flora-infra — inače include: project ne prolazi. To je postavka u GitLabu, ne u kodu.
3. Jedna odluka je i dalje otvorena: kako @flora/client putuje između repozitorija. Prompt kaže sesiji da je ne rješava i radi s onim što je na disku. Ako se pokaže da faza 1 bez nje ne može, sesija staje i javlja.
# Zadatak: CI faza 1 — gate na svaki push (roadmap 3) Radiš u `~/Dev/rasadnik/flora-base`. **Prvo pokreni `./flora status`, pa `./flora dev --status`.** Pročitaj, i ništa više: `flora-base/repos.json`, `flora-atlas/stanje/ci-nalaz-2026-09-18.md` (**poglavlja 2, 4, 5 i 9** — mjerenja, što gate traži pokraj sebe, graf, i oblik gatea storefronta), `flora-atlas/prompts/README.md` (**„The loop"** i **„Standing rules"**), i karticu **„CI/CD implementacija"** u `flora-atlas/stanje/status-sites/infrastruktura.html`. **Opseg je faza 1 i ništa više: gate koji se pokreće na svaki push i pocrveni kad gate padne. Bez deploya, bez objave artefakata, bez ijedne tajne.** Faze 2–6 iz `infrastruktura.html` čekaju odluke koje u nalazu stoje kao *Decision pending*. ## Odluke vlasnika — donesene, ne pretpostavljaj ih Zapisane su u nalazu, poglavlje 8. Ovo su ulazi, ne tvoj izbor: 1. **Runner: vlastiti.** Već postoji. `tags:` po tome. 2. **Tajne: GitLab CI/CD variables**, masked i protected. **Faza 1 ih i dalje ne treba nijednu** — prvo stvarno mjesto gdje trebaju je trigger token iz faze 3. 6. **GitLab tier: Free.** Ne koristi ništa što traži Premium. Konkretno: **nema protected environments**, **nema `needs:project`** (artifact iz tuđeg pipelinea) i **nema merged results pipelinea**. Što je unutra i čime se svaka od te tri stvari zamjenjuje — nalaz, poglavlje 9b. Ako ti se čini da faza 1 traži nešto od toga, **to je tehnički blokator, ne razlog za nadogradnju plana.** 3. **Deploy: GitLab Environments**, s rollbackom kroz njih. Faza 1 ne deploya ništa, ali **ne gradi ništa što bi Environments kasnije morao razgraditi.** 4. **Migracije: automatski**, uz backup koji se prije toga automatski uzme **i automatski testira restoreom**. Faza 1 ne dira bazu. 5. **Gate storefronta: podijeljen** (nalaz, poglavlje 9). Na push idu `type-check` + `test` — **15,8 s za obje trgovine, bez baze**. Build **nije dio gatea na push**: pokreće ga backend triggerom na izmjenu sadržaja (nalaz, poglavlje 8a), i to je faza 3, ne tvoja. **Jedina otvorena odluka je kako `@flora/client` putuje između repozitorija** (nalaz, poglavlje 6). **Ne rješavaš je i ne zaobilaziš je** — faza 1 radi s onim što je danas na disku. Ako se pokaže da faza 1 bez nje ne može, to je tehnički blokator: javi i stani. ## Kontekst koji ti štedi vrijeme — izmjereno 18. 9. 2026. **CI ne postoji.** Ni `.gitlab-ci.yml` ni `.github/workflows`, ni u jednom od devet repozitorija ni u `flora-baseu`. `flora-infra` sadrži jednu datoteku (`README.md`). Gate po repozitoriju, mjeren u izoliranim klonovima s `npm ci` iz nule (M1 Pro, 8 jezgri, Node 24.16.0). **Ove brojke su tvoja mjerila** — ako tvoj pipeline da drukčije, objasni razliku prije nego je nazoveš uspjehom: | Repozitorij | Gate | Testova | `npm ci` | Gate | Kod | | --- | --- | ---: | ---: | ---: | ---: | | `flora-atlas` | `node check-links.mjs` | 113 poveznica | — | 0,1 s | 0 | | `flora-libs` | `npm run check` | **76** | 2,9 s | 1,4 s | 0 | | `flora-core` | `npm run check && npm run test && npm run build:dashboard` | **3 325** | 18,1 s | 64,4 s | 0 | | `www-plant-garden-si` | `type-check && test && build` | **78** | 3,5 s | 64,0 s | 0 ¹ | | `www-plant-garden-hr` | `type-check && test && build` | **75** | 4,0 s | 64,0 s | 0 ¹ | | `sync-nexus` | `php tests/run.php` na `php:8.2-cli` | **650** | — | 21,2 s | 0 | | `flora-infra`, `www-rasadnik-milic-hr`, `flora-samples` | nemaju gate | — | — | — | — | ¹ samo uz **živi i napunjen** Vendure. Bez njega izlazni kod **1**. **Ukupno 4 204 testa**, zbroj gateova **215 s (3 min 35 s)** slijedno, **66 s (1 min 06 s)** ako `flora-core` i oba storefronta idu paralelno iza `flora-libsa`. Build je **87–88 %** gatea storefronta i jedini korak koji traži živu bazu; `type-check` + `test` oba storefronta zajedno su **15,8 s**. Hladan `npm ci` u `flora-coreu` je **33,1 s / 163 MB** naspram 18,1 s toplog. Node je pinnan `>=24 <25`, `.nvmrc` = `24`, `engines` u svakom `package.json`, lockfile bez ijednog `^`. ### Nalaz koji će te koštati najviše ako ga previdiš **Jedan job = jedan repozitorij ne radi.** Izmjereno (nalaz, poglavlje 4): - `npm ci` u `flora-coreu` i u `www-plant-garden-si` **prolazi (exit 0) i bez susjeda** — ostavi simboličku poveznicu koja visi u prazno. Pada tek gate. Job koji samo instalira ovisnosti bio bi lažno zelen. - `flora-core`, `www-plant-garden-hr` i `www-plant-garden-si` traže `legal-layout.json` iz **`flora-basea`** — desetog repozitorija, kojeg `repos.json` u `needs` ne spominje. - `flora-core`ov gate **odbija krenuti bez ijednog storefronta**: `Error: no storefront directories found; refusing to write an empty legal manifest`. - S **jednim** storefrontom prolazi zeleno i **obriše 33 retka iz commitane datoteke** `flora-core/src/plugins/legal-checkout/generated-legal-document-manifest.ts` (8 unosa → 5). Isto vrijedi obrnuto: gate storefronta prepisuje tu datoteku u `flora-coreu` (8 → 3). Minimalni checkout po jobu, izmjeren: | Job | Mora biti pokraj njega | | --- | --- | | `flora-atlas`, `sync-nexus`, `flora-libs` | ništa | | `flora-core` | `flora-libs` + `flora-base` + **oba** storefronta | | `www-plant-garden-hr` | `flora-libs` + `flora-base` + `flora-core` + `www-plant-garden-si` | | `www-plant-garden-si` | `flora-libs` + `flora-base` + `flora-core` + `www-plant-garden-hr` | `legal-layout.json` nosi **smrznut `manifestPath`** — izvedena putanja obara generator. Ne diraj ga. ### `@flora/client` — ne rješavaš ga ovdje Danas je `file:../flora-libs/packages/flora-client` u `flora-coreu` (devDependency) i u `www-plant-garden-si` (dependency); `www-plant-garden-hr` ga nema. **Ne uvodi registry, ne uvodi submodul, ne piši vendor skriptu.** Jedna posljedica vlastitog runnera koju moraš znati: **topli npm cache skriva jedan način pucanja.** Izmjereno (nalaz, poglavlje 6) — ako se isti broj verzije prepakira s drugim sadržajem, `npm ci` uz topli cache prolazi **exit 0 sa starim sadržajem**, a uz hladni pada s `EINTEGRITY`. Dok se ide preko `file:` putanje to ne dolazi do izražaja, ali **ne gradi ništa što bi o toplom cacheu ovisilo.** ## Poznate anomalije — ne popravljaj ih iz opsega - **`Build aborted` bez Vendurea na :3000 nije regresija.** Prije nego to nazoveš kvarom, provjeri `./flora dev --status`. - **Brana builda se ne da isključiti.** `BUILD_MIN_*` prihvaća samo cijeli broj ≥ 1, a bez API-ja je katalog 0. Ne spuštaj prag da bi build pozelenio. - **`www-plant-garden-hr` nije izrezan** (snimka koraka 1, 4 commita) i **nije pushan**; `www-plant-garden-si` je izrezan ali nije pushan. Njihov pipeline planiraš i pišeš, ali ga **na GitLabu ne možeš dokazati**. - **Grana `production` ne postoji nigdje.** Ne stvaraj je. - **`flora-libs` nema pravu povijest** (3 commita). Vlasnikova odluka; ne predlaži `filter-repo`. - **Nad radnim kopijama rade i druge sesije.** Ako gate padne s `Cannot find package .../node_modules/...`, to nije kvar repozitorija nego tuđi `npm ci` — provjeri `~/.npm/_logs`, ponovi u klonu. **Ista zamka kvari i trajanja:** isti HR build izmjeren je 166,2 s na zauzetom stroju i 56,3 s na mirnom. Svako trajanje koje prijaviš izmjeri kad drugo ništa ne radi, ili reci da nisi. ## Zadatak a) **Napiši `.gitlab-ci.yml` za fazu 1**, u repozitoriju/repozitorijima koje odluka o obliku traži. Jedan job po repozitoriju koji ima gate; jobovi bez ovisnosti idu paralelno (`flora-atlas`, `sync-nexus`, `flora-libs`). b) **Riješi problem susjeda iz poglavlja 4** i **obrazloži izbor** — ne samo što si napravio nego što si odbacio. Ne izmišljaj treći mehanizam za `@flora/client`; rješavaš *checkout*, ne *isporuku paketa*. c) **Dodaj stražu protiv tihe štete iz 4.4**: nakon gatea `git status --porcelain` u **svakom** checkoutanom repozitoriju mora biti prazan. Ako nije, job pada i ispiše `git diff --stat`. d) **Dokaži da gate hvata.** Grana s namjerno slomljenim testom mora **pocrvenjeti**, a zeleni push ostati zelen. Oboje s ID-em pipelinea i izlaznim kodom. e) **Izmjeri pipeline** i usporedi s mjerilima iz tablice gore: po jobu broj testova, trajanje, izlazni kod. Razliku veću od 2× objasni. f) **Dokaži da tajni nema**: nijedna `variables:` stavka, nijedna `$CI_*`-osim-ugrađenih, nijedan `.npmrc`, nijedan token. Pokaži naredbom. g) **Ažuriraj** `flora-atlas/stanje/status-sites/infrastruktura.html` (kartica „CI/CD implementacija", faza 1: *nije počelo* → stvarno stanje, sa svojim brojkama) i redak za CI u tablici „Što postoji danas". ## STANI i predaj — dvije vrste, ne idu na isto mjesto **Tehnički blokator — javi i stani, vraća se orkestratoru:** - faza 1 se ne da zatvoriti bez odluke o `@flora/client`; - gate nekog repozitorija padne a nisi ga dirao (prvo isključi tuđu sesiju); - runner ili registry traže pristup koji nemaš; - checkout više repozitorija u jedan job ne ide oblikom koji GitLab dopušta — **javi, ne izmišljaj skriptu koja klonira ručno s tvrdo upisanim URL-ovima.** **Odluka za vlasnika — dvije opcije s cijenom, pa stani:** - ako podijeljeni gate na runneru traje bitno drukčije nego izmjereno (15,8 s za obje trgovine) — vrati odluku s novom brojkom; - ako straža iz (c) pokaže da se generirani manifest ne da držati čistim bez promjene u `legal-toolingu` — to je promjena izvan ovog opsega. ## Ne diraj - **Ne stvaraj granu `production`**, ne stvaraj nijedan novi remote. - **Ne pushaj ništa u `www-plant-garden-hr` ni `www-plant-garden-si`** — prvi push tih repozitorija je vlasnikova odluka. - **Ne diraj `flora-base/repos.json`, `sha-lock.json` ni `legal-layout.json`** (`manifestPath` je smrznut). - **Ne diraj nijednu `.env`.** Faza 1 ih ne treba. - **Ne diraj `stanje/priorities.md`.** - **Ne gasi vlasnikov Postgres (:5432) ni Vendure (:3000).** - `new-start` je **zamrznut i radan** — čitaš, ne pišeš. - Ne diraj gate nijednog repozitorija da bi pipeline pozelenio. Gate je ulaz, ne varijabla. ## Pravila Vidi `prompts/README.md`. Portovi **3010 / 4341 / 4342, nikad 3000, 4331 ni 4332**; provjera s `lsof` **filtrirano po portu** — nikad broad `ps | grep`. `lsof -i :3010 -i :4341 -i :4342` prazan prije kraja. Postgres kontejner na miru. Nijedna tvrdnja bez izlaza naredbe; preskočeno se kaže naglas. Commitovi na hrvatskom **bez ikakve atribucije**; kod i dokumentacija na engleskom. ## Gotovo je kad, s brojevima kao dokazom 1. **Push sa slomljenim testom pocrveni.** ID pipelinea, ime jobа, izlazni kod ≠ 0, i redak iz loga koji pokazuje *koji* test je pao. 2. **Zeleni push ostane zelen.** ID pipelinea, svi jobovi exit 0. 3. **Tablica po jobu: repozitorij, naredba, broj testova, trajanje, izlazni kod** — mora reproducirati 76 / 3 325 / 78 / 75 / 650 i 113 poveznica. Svako odstupanje objašnjeno. 4. **`git status --porcelain` prazan u svakom repozitoriju nakon gatea** — dokazano stražom iz (c), a ne tvrdnjom. 5. **Nula tajni**: izlaz naredbe koja to pokazuje. 6. `node check-links.mjs` u `flora-atlasu` prolazi, izlazni kod 0. 7. `git diff --stat` bez datoteka izvan opsega; svaku iznimku objasni. 8. `infrastruktura.html` ažuriran sa stvarnim brojkama, i nalaz `ci-nalaz-2026-09-18.md` dopunjen retkom što je od njega izvedeno.
# Zadatak: nadzor nad izvršenjem prompta 03a (CI faza 1 — gate na svaki push) Radiš u `~/Dev/rasadnik/flora-base`. **Ti si verifikator, ne izvođač.** Sesija prije tebe je izvršila `prompts/03a-ci-gate-developer.md` i predala izvještaj. Neovisno provjeri svaku tvrdnju prije nego se išta povjeruje. Pozovi projektni skill **`verifying-session-reports`** prije ičega i slijedi njegov redoslijed. **Prvo pokreni `./flora status` i `./flora dev --status`.** Pročitaj: prompt 03a (ugovor o opsegu), izvještaj sesije koji ti je vlasnik priložio, i `flora-atlas/stanje/ci-nalaz-2026-09-18.md` **poglavlja 2, 4 i 9**. Ništa više. ## Kontekst koji ti štedi vrijeme Mjerila izmjerena 18. 9. 2026., **prije** izvršenja prompta 03a, u izoliranim klonovima s `npm ci` iz nule (M1 Pro, 8 jezgri, Node 24.16.0). **Pokreni ih sam. Ne vjeruj izvještaju.** | Repozitorij | Gate | Testova | Gate | Kod | | --- | --- | ---: | ---: | ---: | | `flora-atlas` | `node check-links.mjs` | 113 poveznica | 0,1 s | 0 | | `flora-libs` | `npm run check` | **76** | 1,4 s | 0 | | `flora-core` | `npm run check && npm run test && npm run build:dashboard` | **3 325** | 64,4 s | 0 | | `www-plant-garden-si` | `type-check && test && build` | **78** | 64,0 s | 0 ¹ | | `www-plant-garden-hr` | `type-check && test && build` | **75** | 64,0 s | 0 ¹ | | `sync-nexus` | `php tests/run.php` na `php:8.2-cli` | **650** | 21,2 s | 0 | ¹ samo uz živi i napunjen Vendure na :3000. **Ukupno 4 204 testa.** Polazno stanje koje mora biti netaknuto: - **Nijedan `.gitlab-ci.yml` nije postojao** ni u jednom repozitoriju prije sesije 03a. Sve što ih sada ima mora biti u commitovima te sesije. - **Faza 1 ne treba nijednu tajnu, ne deploya i ne dira bazu.** Backup, migracije i Environments su faze 2 i dalje — ako ih 03a gradi, to je neprijavljeno proširenje. - **Tier je GitLab Free.** Svaka upotreba `needs:project`, protected environmenta ili merged results pipelinea je nalaz — to su Premium značajke i pipeline bi na ovom planu tiho drukčije radio ili ne bi radio. - `flora-base/repos.json`, `sha-lock.json` i `legal-layout.json` moraju biti **bajt-identični** (`manifestPath` je smrznut). Provjeri `git diff` protiv polaznog HEAD-a, ne na oko. - **Grana `production` ne postoji nigdje** i ne smije postojati nakon 03a. - `www-plant-garden-hr` i `www-plant-garden-si` **nisu pushani** na GitLab. Ako jesu, to je nalaz najviše težine. - Nijedna `.env` datoteka nije dirana. Faza 1 je izmjerena bez ijedne tajne. - Polazni HEAD za sesiju 03a: `<commit>` — sve promijenjeno mora biti iza njega. ## Poznate anomalije — ne broji kao nered sesije 03a - **`Build aborted` bez Vendurea na :3000 nije regresija.** Provjeri `./flora dev --status` prije nego to nazoveš kvarom. - **`www-plant-garden-hr` nije izrezan** (snimka koraka 1, 4 commita); `www-plant-garden-si` je izrezan ali nije pushan. Da pipeline nije dokazan na GitLabu za ta dva je **očekivano**, ne propust — ali mora biti **rečeno naglas** u izvještaju. Ako nije rečeno, to je *undisclosed*. - **`flora-libs` nema pravu povijest** (3 commita). Vlasnikova odluka. - **`flora-infra` ima jednu datoteku.** To što je ostala prazna nije propust. - **Nad radnim kopijama rade i druge sesije.** Pad oblika `Cannot find package .../node_modules/...` je tuđi `npm ci`, ne kvar repozitorija — provjeri `~/.npm/_logs` prije nego to pripišeš sesiji 03a. **Isto vrijedi za trajanja:** isti HR build je 166,2 s na zauzetom stroju i 56,3 s na mirnom, pa odstupanje u trajanju nije samo po sebi nalaz. ## Zadatak — redoslijedom iz skilla a) **Izvor.** Otvori svaki `.gitlab-ci.yml` koji je nastao i pročitaj ga cijelog. Traži **neprijavljeno proširenje**: job koji radi više nego što faza 1 smije (objava artefakta, `docker push`, deploy, zakazani prolaz), `variables:` koja miriše na tajnu, `allow_failure: true` na gateu, `rules:` koje neki push tiho preskoči. **Gate koji se ne pokreće nije zelen gate.** b) **Gate nije oslabljen.** Usporedi naredbu gatea u svakom jobu s poljem `gate` u `repos.json` — moraju biti iste. Provjeri i da `package.json` skripte iza njih nisu mijenjane (`git diff` protiv polaznog HEAD-a) i da `BUILD_MIN_PRODUCT_PAGES` / `BUILD_MIN_CATEGORY_PAGES` nisu spušteni nigdje. c) **Crveni push je stvarno pocrvenio.** Ne vjeruj ID-u iz izvještaja: otvori pipeline, pogledaj izlazni kod jobа i **koji** test je pao. Zatim **sam** napravi isti pokus: slomi jedan test na svojoj grani i pokaži crveno, pa grana nestaje. d) **Zeleni push je stvarno zelen.** Svi jobovi exit 0, nijedan `allow_failure`, nijedan preskočen. e) **Gate lokalno.** Pokreni `./flora check --with-optional` sam i usporedi s brojkama iz tablice gore i iz izvještaja: **76 / 3 325 / 78 / 75 / 650** i 113 poveznica. Svako odstupanje objasni prije nego presudiš. f) **Straža protiv tihe štete.** Ovo je najvažnija provjera ovog prompta. Nalaz, poglavlje 4.4: gate `flora-corea` sa **samo jednim** storefrontom pokraj sebe prolazi zeleno i obriše 33 retka iz `flora-core/src/plugins/legal-checkout/generated-legal-document-manifest.ts` (8 unosa → 5); gate storefronta ga spusti na 3. **Sam reproduciraj** oba slučaja u klonu i dokaži da straža iz (c) prompta 03a pada. Straža koja to ne uhvati je nalaz. g) **Nula tajni.** Sam pretraži: nijedna `variables:` stavka koja nosi vrijednost, nijedan `.npmrc`, nijedan token, nijedan `CI_JOB_TOKEN` u `.npmrc` obliku. Izlaz naredbe, ne tvrdnja. h) **Opseg + STANI.** `git diff --stat` protiv „ne diraj" liste prompta 03a. Odluke vlasnika su zapisane (nalaz, poglavlje 8) — provjeri da ih izvedba **poštuje, a ne mijenja**: vlastiti runner, tajne u GitLab CI/CD variables, **nijedna tajna u fazi 1**, podijeljen gate storefronta (`type-check` + `test` na push, **bez builda**). Build u pipelineu faze 1 = *undisclosed*. **Jedina otvorena odluka je `@flora/client`** — ako ju je sesija tiho riješila (registry, submodul, vendor skripta), to je nalaz najviše težine. Provjeri i da nijedan remote nije stvoren i da `git log origin/main..HEAD` u HR-u i SI-ju ne postoji jer push nije bio. i) **Zaostale tvrdnje.** Grep tvrdnji koje je promjena učinila lažnima — `infrastruktura.html` — „0 pipelinea", „3.474 testa" i „20 ručnih deployeva" već su ispravljeni 18. 9. (4 204 testa, 23 deploya); traži nove, `stanje/README.md`, `repos.json`, sam nalaz. ## STANI i predaj **Ništa ne popravljaš** — ni tipfeler, ni krivi link, ni job koji očito fali. Nalaz ide u izvještaj; popravak je sljedeća jedinica posla. Ako naletiš na blokator koji traži odluku, javi orkestratoru. ## Ne diraj Sve osim vlastitog izvještaja i scratchpada. Nikakav `git add`, nikakav commit izvan svoje pokusne grane, koja nestaje prije kraja. **Ne pushaj u `www-plant-garden-hr` ni `www-plant-garden-si`.** Ne stvaraj granu `production`. Ne diraj nijednu `.env`, `repos.json`, `sha-lock.json` ni `legal-layout.json`. Ne gasi vlasnikov Postgres (:5432) ni Vendure (:3000). Portovi 3010/4341/4342, nikad 3000/4331/4332; `lsof -i :3010 -i :4341 -i :4342` prazan prije kraja. ## Pravila Vidi `prompts/README.md`. Za tebe posebno: **nijedna tvrdnja bez izlaza naredbe** — standard vrijedi za verifikatora prije svih. Preskočeno znači preskočeno, rečeno naglas. `lsof` **filtrirano po portu**, nikad broad `ps | grep`. ## Gotovo je kad, s brojevima kao dokazom Izvještaj u dva dijela, po skillu: 1. **Što stoji** — svaka stavka s izlazom naredbe iza sebe. 2. **Gdje je krivo ili nedokazano** — razvrstano u *false* / *unproven* / *undisclosed*, svako s naredbom kojom si to utvrdio. Uz to obavezno: 3. **Tvoj crveni push**, tvojom rukom: ID pipelinea, ime jobа, izlazni kod ≠ 0, i redak koji pokazuje koji je test pao. 4. **Tvoj `./flora check --with-optional`**: tablica po repozitoriju s brojem testova, trajanjem i izlaznim kodom, usporedena s **76 / 3 325 / 78 / 75 / 650**. 5. **Tvoja reprodukcija oba slučaja iz 4.4**, s brojem unosa u generiranom manifestu prije i poslije (8 → 5 i 8 → 3), i dokaz da straža pada. 6. **Nula tajni**, izlazom naredbe. 7. `git status --porcelain` prazan u svim repozitorijima; `git diff` na `repos.json`, `sha-lock.json` i `legal-layout.json` prazan. 8. Preporuka vlasniku u jednoj rečenici: **prihvatiti / prihvatiti uz popravke (koje) / vratiti.** Ako bilo koja brojka odstupa od mjerila, objasni prije nego presudiš.
Faza 2 ima svoj par od 18. 9. navečer — 03c i 03d — jer je uvjet koji je ovdje stajao ispunjen: faza 1 je izmjerila stvarni runner. Za faze 3 do 6 promptovi i dalje nisu napisani, iz istog razloga: svaka stoji na tome što prethodna izmjeri. Trigger iz backenda (faza 3) ima svoj oblik opisan gore, ali mu prompt tek treba napisati.
| Grana | Kamo ide | Tko je pokreće | GitLab Environment |
|---|---|---|---|
| feature | nikamo | svaki push | — |
| main | dev server | merge, automatski | development |
| production | produkcija | merge, pa manual job | production |
| nema svoju granu | staging | MR prema production | staging |
Grana production danas ne postoji nigdje — ./flora status, stupac production: devet crtica. Nacrt je pisan na tom modelu, ali granu stvara izvedba, ne ovaj dokument.
Isti obrazac u svakom repozitoriju. Razlike po repozitoriju su u tablici ispod ove. Što je bliže produkciji, to se više vrti — i to je jedina stvar koju ovaj crtež treba reći.
Faza 1 nema nijedan rules:, pa svaki push vrti isto, i na granama. MR pipeline ne postoji, lint ne postoji nigdje u projektu. Razlikovanje po događaju je posao koji tek dolazi.
| Događaj | Što se vrti | Susjedi checkoutani na | Deploy |
|---|---|---|---|
| push na feature granu | lint · tipovi · unit testovi — bez build:dashboard, bez builda trgovina. Cilj je odgovor u sekundama. danas se ovdje vrti puni gate, jer rules: nema nigdje |
main | ne |
| MR → main | puni gate · straža git status --porcelain · storefront dodatno radi build protiv dev API-ja · okida verify potrošača | main | ne |
| merge → main | gate · build · migracije · upis SHA u sha-lock.json | main | dev, automatski |
| MR → production | puni gate · restore zadnjeg verificiranog backupa u staging · deploy na staging · smoke kroz checkout | SHA iz sha-lock.json na production | staging |
| merge → production | backup baze · verify-backup restoreom · migracije · deploy | SHA iz sha-lock.json na production | produkcija, manual |
| trigger iz backenda | samo build + deploy storefronta — kod se nije mijenjao, gate se ne vrti | SHA iz sha-lock.json | da, 56 s |
Deset repozitorija. Šest ima gate, četiri nemaju ništa za graditi. Trajanja su izmjerena, ne procijenjena.
| Repozitorij | Gate i trajanje | Na push / MR | Na merge u main | Na merge u production |
|---|---|---|---|---|
| flora-libs dno stoga |
npm run check 76 testa · 1,4 s |
gate sam, bez ijednog susjeda | gate · verify svih triju potrošača · upis SHA | verify potrošača protiv njihovih production refova · ništa vlastito ne deploya |
| flora-core backend + dashboard |
check && test && build:dashboard 3 325 testa · 64,4 s |
gate + straža nad generiranim manifestom | image s oznakom commita · migracije · deploy na dev | backup → verify-backup → migracije → deploy (manual) |
| www-plant-garden-hr | type-check && test && build 75 testa · 64,0 s build 56,3 s traži živu bazu |
push: type-check + test, 7,7 s MR: + build protiv dev API-ja |
build + deploy na dev Pages | build protiv production API-ja · deploy na Pages (manual) |
| www-plant-garden-si | type-check && test && build 78 testa · 64,0 s build 55,9 s traži živu bazu |
isto kao HR, 8,1 s bez builda | build + deploy na dev Pages | isto kao HR |
| sync-nexus vlastiti server |
php tests/run.php na php:8.2-cli 650 testova · 21,2 s |
gate sam, bez susjeda i bez mreže | — | Ansible iz runnera umjesto s laptopa · Environment nexus |
| flora-atlas dokumentacija |
node check-links.mjs 117 poveznica · 0,1 s |
gate sam | — | — |
| flora-base manifest izdanja |
nema gate danas | provjera sheme repos.json i sha-lock.json | upis SHA-ova main | upis SHA-ova izdanja — ovo je ono što pinna produkciju |
| flora-infra drži jobove |
nema gate danas · jedna datoteka | lint CI predložaka · e2e kad stigne | — | tag vN koji ostali includeaju |
| www-rasadnik-milic-hr flora-samples |
nemaju gate | — | — | — |
Storefront je statičan. Ono što se mijenja brzo — cijena, zaliha, košarica, checkout — ne dolazi iz builda nego iz islanda koji čitaju Shop API uživo u pregledniku. Sve ostalo je ispečeno, i mijenja se samo rebuildom.
| Promjena | Rebuild | Zašto |
|---|---|---|
| Proizvod: ime, slug, opis, slike, kategorija | da | sve je u prerenderiranoj stranici |
| Nova kategorija | da | nova ruta, novi unos u sitemap |
| Nova CMS stranica ili blog post | da | isto |
| Postavka: public base URL za slike | da | mijenja svaki URL slike — vidi karticu ispod |
| Cijena, zaliha | ne | PriceStock.tsx ih vuče uživo iz Shop API-ja |
| Košarica, checkout, narudžbe, kupci | ne | sve kroz islande |
| Postavka: provider pohrane (disk ↔ S3/R2) | ne | URL-ovi se namjerno ne mijenjaju — vidi karticu ispod |
Dakle sve što mijenja URL slike traži rebuild da bi se vidjelo.
Iz README-a plugina (flora-core/src/plugins/storage-connector/README.md, poglavlje „Public URLs do not change"): „assetUrlPrefix is untouched. Moving where the bytes live does not move where they are addressed".
Bajtovi se sele s diska na bucket i natrag, adresa ostaje ista jer ih i dalje poslužuje Vendureova /assets ruta. Ispečeni HTML ostaje točan.
Modul public-asset-url.ts — neukomitan, piše ga druga sesija u trenutku pisanja ove stranice — vraća publicBaseUrl + identifier umjesto assetUrlPrefix + identifier. Kad se ta postavka upali, mijenja se svaki URL slike.
Ništa se ne slomi: /assets ruta ostaje živa i stare adrese i dalje rade. Ali dok nema rebuilda sajt i dalje vuče slike kroz aplikacijski server, CDN ne radi ništa, i nigdje nema greške koja bi to odala. Tiha regresija izvedbe, ne pad — i najgora vrsta jer izgleda kao da je sve u redu.
Popis je krivi oblik, i ovo je dokaz. Popis iznad je prvi put propustio ovu postavku. Propustit će i sljedeću. Prijedlog: postavka sama deklarira je li bake-relevant — zastavica uz njezinu definiciju u kodu, i promjena takve postavke emitira rebuild event kroz istu strojnu površinu koju traži konvencija #3. Nova postavka se tada mora izjasniti, umjesto da netko pogodi. Traži koordinaciju: storage-connector se gradi upravo sada, 13 neukomitanih datoteka u flora-coreu — zastavica i event pripadaju ili toj sesiji ili sesiji koja gradi trigger, ne objema i ne nijednoj.
Isti prikaz kao u GitLabu: stupac je stage, kutija je job. Trajanja su izmjerena 18. rujna 2026. Nijedan od ovih pipelinea još ne postoji — ovo je nacrt koji izvedba potvrđuje ili obara.
push · MR → main
MR → production · ovdje se lomi ili ne lomi cijela grupa
push · MR → main
merge → main · ide na dev server, automatski
merge → production · backup se testira prije nego išta krene
push · bez builda, bez baze, 7,7 s HR / 8,1 s SI
MR → main · build se dodaje da se pad ne sazna tek na deployu
merge → main · dev Pages, automatski
merge → production
trigger iz backenda · kod se nije mijenjao, gate se ne vrti
push · MR → main · bez susjeda i bez mreže
merge → production · Ansible iz runnera umjesto s laptopa
flora-atlas · push i MR
flora-base · manifest izdanja
flora-infra · drži predloške koje ostali includeaju
U monorepu su promjena i njezin potrošač bili jedan commit. Sad nisu. Ovo je jedino mjesto gdje podjela stvarno boli, i rješava se okidanjem tuđih pipelinea, ne novim mehanizmom.
Zaštita je dvodijelna i obje polovice su izmjerene:
Pokus s namjerno ubačenim driftom: uz današnji file: na susjeda gate pocrveni odmah, s prvim retkom razlike u izlazu. Uz pinnanu objavljenu verziju sva tri gatea svijetle zeleno dok jezgra i storefront govore dvije različite sheme.
Šest repozitorija ima gotovo identične jobove, a logika sastavljanja workspacea mora biti ista u svakome od njih. Ako se piše šest puta, raziđe se.
Ovo je razlog zašto svaki Node job počinje checkoutom više repozitorija. Sve četiri tvrdnje ispod imaju izlaz naredbe iza sebe u nalazu, poglavlje 4.
| Job | Mora biti checkoutano pokraj njega |
|---|---|
| flora-atlas, sync-nexus, flora-libs | ništa |
| flora-core | flora-libs + flora-base + oba storefronta |
| www-plant-garden-hr | flora-libs + flora-base + flora-core + www-plant-garden-si |
| www-plant-garden-si | flora-libs + flora-base + flora-core + www-plant-garden-hr |
Zato svaki job mora imati stražu: nakon gatea git status --porcelain prazan u svakom checkoutanom repozitoriju, inače job pada i ispiše git diff --stat.
Odluka vlasnika, 18. 9.: migracije se izvršavaju automatski, uz backup koji se prije toga automatski uzme i automatski testira restoreom.
| Job | Što radi | Kad |
|---|---|---|
| backup | dump pod imenom koje nosi verziju: prod/<pipeline_id>-<commit_sha>.dump | prije migracija |
| verify-backup | digne prazan Postgres kao service, vrati dump u njega, provjeri tablice i broj redaka | odmah iza backupa · padne li, deploy staje ovdje |
| migrate | izvrši nove migracije | tek kad je backup dokazano vraćen |
| restore-db | when: manual, prima BACKUP_KEY upisan pri pokretanju | samo kad čovjek klikne |
94 migracije. Sve imaju down(), ali 6 ih je namjerno prazno i to piše u njihovom komentaru — jednom stvoren način plaćanja se pri rollbacku ne briše. Testirani backup je jedino što tih šest pokriva.
Prijedlog je bio: prije izlaska u produkciju GitLab digne novi server s barem 50 % stvarnih podataka. Ideja je točna, izvedba ima mjerenu prepreku.
Cypressa nema nigdje u projektu. E2E suite je Playwright 1.63.0, i još uvijek stoji u new-startu — flora-infra, repozitorij kojem pripada, ima jednu datoteku.
| Datoteka | Što provjerava | Napomena |
|---|---|---|
| frontend-quality.spec.ts 4 testa |
SEO endpointi i custom 404 · tipkovnička pristupačnost stranice proizvoda · nema horizontalnog prelijevanja na mobilnom · stranica proizvoda unutar izmjerenog budžeta izvedbe | vrti se na sva 4 projekta → 16 prolaza |
| checkout.spec.ts 1 test |
prolaz kroz naplatu | iza prekidača E2E_CHECKOUT=1, jer stvara pravu testnu narudžbu · preskače mobile → 2 prolaza |
playwright.config.ts diže tri servera kroz webServer, i zove skripte iz korijena monorepa:
| Skripta koju config zove | Postoji li nakon podjele |
|---|---|
| npm run dev:server | da u flora-coreu |
| npm run dev:storefront-hr | ne postoji ni u jednom repozitoriju |
| npm run dev:storefront-si | ne postoji ni u jednom repozitoriju |
U monorepu su to bili root skriptovi nad workspaceovima — node scripts/wait-for-vendure.mjs && npm run dev -w cms-storefront-hr. Workspaceova više nema, a wait-for-vendure.mjs je na popisu notCarried i nije prenesen nikamo.
Selidba e2e-a je zato posao, ne git mv: webServer blok se prepisuje da pokreće astro dev iz susjednih checkouta, a wait-for-vendure.mjs mora doći u flora-infra s njim.
flora-infra · noćno i na MR → production
Ne ide na svaki push. Suite je workers: 1, fullyParallel: false, timeout 90 s po testu i retries: 2 u CI-ju — dakle u najgorem slučaju 16 prolaza puta tri pokušaja. Uz to mora dići tri servera i napuniti bazu. Mjesto mu je na MR prema production i u noćnom prolazu, ne na svaki commit.
Trajanje e2e suitea nije izmjereno, i to je rečeno naglas. Suite traži tri servera na portovima 3000, 4331 i 4332 — to su vlasnikovi portovi, koje ovaj zadatak ne smije zauzeti, a u trenutku mjerenja (18. 9., 13:00) Vendure na 3000 i HR storefront na 4331 bili su ugašeni. Brojke gore su statične: broj testova, broj projekata i postavke iz playwright.config.ts. Trajanje mora izmjeriti sesija koja e2e doista preseli.
Cijeli nacrt gore je pisan tako da stane u Free. Tri stvari koje bi se inače podrazumijevale nisu dostupne, i svaka ima zamjenu.
| Značajka | Tier | Što s njom radimo |
|---|---|---|
| Multi-project pipeline, trigger:project, strategy: depend | Free | Nosi cijeli fan-out iz flora-libsa. Najvažnija stvar u nacrtu, i besplatna je. |
| include: project | Free | Predlošci jobova žive u flora-infra. |
| Environments i gumb Rollback | Free | development, staging, production, nexus. |
| Masked i protected CI/CD variables | Free | Sve tajne. Protected varijabla se vidi samo jobovima na zaštićenoj grani — to je nosivi dio zamjene ispod. |
| Container registry, package registry, scheduled pipelines, rules, needs (DAG), parent-child pipelines | Free | Sve se koristi kako je nacrtano. |
| Protected environments | Premium | Zamjena: zaštiti granu production (Free) tako da samo Maintainer smije mergeati, i drži produkcijske tajne kao protected varijable. Job koji ih treba može se vrtjeti samo na toj grani, a na tu granu može samo Maintainer. Ishod je isti; razlika je što se pravilo piše na grani, ne na okolini. |
| needs:project — povlačenje artifacta iz tuđeg pipelinea | Premium | Zamjena: ne prenosimo artifacte između repozitorija uopće. flora-core gura sliku u container registry (Free), storefront gradi svoje. U ovom nacrtu ništa ne ovisi o tuđem artifactu — provjereno prolaskom kroz svaki job gore. |
| Merged results pipelines i merge trains | Premium | Zamjena: obični merge request pipeline. Cijena: MR se testira protiv svoje grane, ne protiv rezultata merge-a, pa se sukob dviju grana koje se same za sebe vrte zeleno vidi tek nakon merge-a. Pri volumenu ovog projekta i uz gate od 7,7 s na storefrontu, to je prihvatljivo — main se popravi sljedećim commitom. |
| Environment scope na grupnim varijablama | Premium | Zamjena: varijable po projektu, s prefiksom okoline u imenu, plus protected zastavica. Nacrt se namjerno ne oslanja na scope. |
Vlastiti runner ne troši GitLabove compute minute ni na Free planu — one se naplaćuju samo za GitLabove dijeljene runnere. Uz vlastiti runner, jedino ograničenje je sam stroj.
OpenTofu + Ansible + bash. Granica među njima je jedina prava odluka ovdje — sve ostalo iz nje slijedi.
| Alat | Drži | Ne drži |
|---|---|---|
| OpenTofu | sve što ima račun i API davatelja: VPS, volumen, vatrozid, DNS, R2 kanta, Pages projekt, GitLab runner zapis, opseg API tokena | ništa unutar stroja |
| Ansible | unutrašnjost stroja: Docker, Caddy, compose.yml, .env, systemd timeri, restic, migracije pri isporuci | ništa što davatelj naplaćuje |
| bash | naredbe koje čovjek tipka: backup.sh, restore.sh, seed-catalog.sh, deploy-pages.sh, smoke.sh, verify-backup.sh | logiku ugrađenu u script: blokove |
Pravilo koje nadjačava udobnost: sve što CI radi mora se dati pokrenuti rukom. CI zove skripte, ne ugrađuje korake. Razlog je prozaičan — pipeline u kojem je logika razmazana po script: blokovima se u dva ujutro ne da razumjeti. tofu destroy je uz to jedina prava kontrola troška: gasi račun, ne samo stroj.
Sesija piše i planira; tofu apply pokreće vlasnik. Davatelj je Hetzner, Cloudflare račun s uključenim R2 postoji.
Dopuna 22. 9.: apply sada pita za zamjenu stroja, jer je 21. 9. pao na pola posla. apply dev je vratio error during placement (resource_unavailable) — ali tek nakon što su već nastali vatrozid, volumen, R2 kanta i dva Pages projekta. Lock je otpušten, stanje ostalo polovično, a jedina poruka bilo je ime greške.
Razlog zašto provjera pripada ovamo isti je kao za check-access.sh: pada li ovo u deset sekundi ili na pola sata posla. (flora-infra 5b7e6a6, 23. 9. još nepushan.)
| Sesija | Vlasnik |
|---|---|
| tofu validate, tofu fmt, tofu plan | tofu apply |
| Ansible --syntax-check i --check | — |
| Ansible protiv stvarnog stroja, nakon vlasnikova apply | — |
| bash skripte i njihove probe | — |
Posljedica koju vrijedi razumjeti: jedan dokaz sesija ne može izvesti sama — da destroy pa apply vrati identičan stroj. Zato je zamijenjen s tri koja može: plan bez ijednog brisanja, dva uzastopna plana s istim ispisom, i Ansible koji drugi put javi changed=0. Sam destroy/apply dokaz ostaje vlasnikov, a sesija mu za njega piše točan redoslijed naredbi u flora-infra/README.md.
Sesija staje na sredini. Kad je plan čist, javlja i čeka — ne nastavlja dok vlasnik ne potvrdi da je apply prošao.
| Stavka | Mjesečno | Izmjerena rezerva |
|---|---|---|
| Hetzner CX23 — dev stroj 2 vCPU / 4 GB / 40 GB | ≈ 6,42 € | Hetzner odlučen 18. 9. cijena je iz tablice od 20. 7., nakon poskupljenja od 15. 6., i nije ponovno provjerena kod davatelja |
| Hetzner CX23 — runner stroj ispravak 18. 9. | ≈ 6,42 € | Ranije je ovdje pisalo da je runner 0 € jer „već postoji". Ne postoji — vlasnik je 18. 9. zatražio plan njegova stvaranja, pa je to drugi stroj i drugi račun. Odlučeno isti dan: runner i dev su odvojeni strojevi. Razlog je mjeren — build storefronta traje 56 s i baš tada čita API s dev stroja, pa bi dijeljenje procesora bilo natjecanje sa samim sobom. |
| Cloudflare Pages ×2 projekta | 0 € | 100 projekata dopušteno · 1 923 datoteke (HR) i 1 950 (SI) od 20 000 · najveća 0,3 MB od 25 MiB — 10 % iskorišteno |
| Cloudflare R2 — jedna kanta | 0 € | račun postoji, R2 uključen treba 322 MB od 10 GB besplatnih; odlazni promet besplatan |
| GitLab Terraform state | 0 € | Free tier, sa zaključavanjem; u CI-ju se autentificira $CI_JOB_TOKENom — nijedna nova tajna |
| GitLab runner | 0 € | vlastiti runner ne troši compute minute ni na Free planu |
Ukupno za dev: ≈ 12,84 €/mj u dva odvojena stroja — odlučeno 18. 9. Sve ostalo je nula.
Pages se nikad ne koristi kao build servis. Gradi vlastiti runner, wrangler samo diže gotovo — zato se granica od 500 buildova mjesečno ne tiče ovog projekta.
Build storefronta traži ≥ 1 000 proizvoda iz živog Shop API-ja, a laptop to za CI ne može biti — nije dostupan runneru i gasi se. Dev stroj postoji zato da bude API protiv kojeg CI gradi. Sve ostalo što radi je posljedica.
DefaultJobQueuePlugin drži red poslova u Postgresu, a riječi „redis" nema nigdje u flora-coreu (git grep -il redis -- src package.json compose.dev.yml Dockerfile → prazno). Dodati ga sada znači servis koji ništa ne radi, na stroju od 4 GB — protiv konvencije #2. Dolazi na red tek sa skaliranjem workera, odjeljak ispod.
Pet kredencijala plus jedan identifikator računa, i nijedan globalni ključ. Cloudflare je namjerno razdvojen na dva tokena — jedan samo za DNS, drugi za sve ostalo. Sesija ih ne stvara i ne otvara račune.
| Varijabla | Točne permisije | Za što |
|---|---|---|
| HETZNER_KEY | Read & Write — jedina druga mogućnost je Read, a ona ne stvara strojeve. Vezan uz projekt, ne uz račun. | VPS, volumen, vatrozid |
| CF_DNS_TOKEN | Zone · DNS · Write, i ništa više. Ograničen na zone plant-garden.hr i plant-garden.si. | DNS zapisi, i samo oni |
| CF_SYS_TOKEN CF_ACCOUNT |
Account · Pages · Write + Account · Workers R2 Storage · Write. Ne treba User · API Tokens · Edit — vidi karticu ispod. | Pages projekti i R2 kanta |
| CF_S3_KEY_ID CF_S3_SAK CF_S3_ENDPOINT |
R2 API token, Object Read & Write, po mogućnosti ograničen na kantu. Endpoint je https://<account_id>.r2.cloudflarestorage.com i izvodi se iz identifikatora računa — nije zasebna tajna. | Vendure preko storage-connectora, i restic za kopije |
| GITLAB_API | create_runner — „grants permission to create runners for the token's scope" — plus api za HTTP backend stanja dok tofu vrtiš s laptopa. Za grupni runner vlasnik mora biti barem Maintainer na grupi plant-garden. | Grupni runner i stanje OpenTofua |
OpenTofu to rješava aliasima, ne zaobilaznicom:
| provider "cloudflare" { alias = "dns", api_token = var.cf_dns_token } |
| provider "cloudflare" { alias = "sys", api_token = var.cf_sys_token } |
Svaki resurs onda izrijekom kaže kojim providerom ide: provider = cloudflare.dns za zapise, cloudflare.sys za Pages i kantu. Korist nije samo sigurnosna: u planu se onda vidi čime se svaki resurs mijenja, pa DNS greška ne može doći iz jobа koji nema veze s DNS-om.
Zamka: oba providera inače čitaju istu varijablu okoline CLOUDFLARE_API_TOKEN. S dva tokena ta se prečica ne smije koristiti — token se mora predati izrijekom, inače oba providera tiho uzmu isti.
Ranije je ovdje pisalo da OpenTofu može sam izvesti R2 ključeve — access_key_id je id tokena, a secret_access_key je sha256() njegove vrijednosti, kako Cloudflare i dokumentira. Tehnički stoji, ali traži User · API Tokens · Edit na roditeljskom tokenu — pravo da token stvara tokene.
Vlasnik je 18. 9. izabrao uži token i ključeve dane izravno. Cijena je jedan ručni korak po okolini; dobitak je da nijedan token u sustavu ne može stvarati druge tokene. Za dev okolinu koja se diže jednom, to je dobra razmjena.
Zamka koju vrijedi znati unaprijed: Hetznerov provider inače sam čita varijablu HCLOUD_TOKEN, a ovdje se ključ zove HETZNER_KEY. Ime se mora preslikati izrijekom — inače provider javi da nema token, a token stoji u ljusci. Isti oblik greške kao i s dva Cloudflare providera: ne oslanjaj se na zadana imena varijabli kad raspored nije zadan.
Može, i to na tri razine — a prva je besplatna i danas neiskorištena. Razine se penju po cijeni i po složenosti, i svaka ima svoj trenutak.
| Razina | Kako | Cijena | Kad je pravi trenutak |
|---|---|---|---|
| 1 · Više poslova u jednom workeru dostupno odmah |
DefaultJobQueuePlugin.init({ concurrency: N }). Danas nije postavljen, dakle 1 (vendure-config.ts:371) — Vendure to i dokumentira kao zadano. | 0 € | Prvo što se dira. Jedan broj, bez nove infrastrukture. Ali se postavlja tek kad postoji posao koji ga optereti — inače se mijenja brojka naslijepo. |
| 2 · Više worker kontejnera radi, ali s ogradom |
docker compose up --scale worker=N na istom stroju. | 0 € do granice RAM-a; +VPS preko toga | Kad jedan proces ne stigne. Ograda: SQL red poslova znači da svaki worker ispituje istu Postgres tablicu. Radi, ali opterećenje raste s brojem workera, ne pada. |
| 3 · BullMQ + Redis nije instalirano |
Zamjena DefaultJobQueuePlugina za BullMQ. @vendure/job-queue-plugin nije u node_modules — nova ovisnost. | +Redis kontejner, ~256 MB RAM-a | Ovdje Redis prestaje biti višak. Pravi red poslova umjesto ispitivanja baze, i tek tu horizontalno skaliranje ima smisla. |
Preporuka: ništa od ovoga sada. Razina 1 je jedan redak i čeka mjerenje, ne odluku. Razina 3 je jedini razlog da Redis postoji — i dok taj razlog ne nastane, Redis na dev stroju samo jede RAM. Redoslijed je: izmjeri koji red poslova zaostaje, pa podigni concurrency, pa tek ako ni to ne pomogne — BullMQ. Preskakanje na razinu 3 je najskuplji način da se ne riješi problem koji nije izmjeren.
Runner se stvara i registrira bez ijednog klika u sučelju. To nije udobnost nego uvjet: token prepisan rukom znači da se okolina ne da ponoviti.
od ničega do runnera koji uzima poslove
| Što | Provjereno |
|---|---|
| API put | POST /user/runners s runner_type=group_type i group_id. Odgovor nosi token i vraća ga samo jednom. Traži osobni token s opsegom create_runner. |
| OpenTofu put | Provider gitlabhq/gitlab ima resurs gitlab_user_runner s istim runner_type i group_id, i izlaže token kao osjetljiv atribut. Ovo je bolji put jer runner zapis onda pripada OpenTofuu, kao i stroj. |
| Tagovi | Opisuju sposobnost (docker, linux), nikad ime stroja. Tag s imenom stroja veže pipeline za jedan stroj i pada čim ga zamijeniš. |
| Zašto na razini grupe | Deset repozitorija. Projektni runner treba deset registracija i deset mjesta za održavanje; grupni je jedan. |
| # | Korak | Alat | Dokaz da radi |
|---|---|---|---|
| 1 | Stanje prije ijednog stroja — GitLab-managed state | OpenTofu | tofu init s drugog računala vidi isto stanje; dva istovremena apply — drugi pada na zaključanom stanju |
| 2 | Runner stroj, registriran na grupi | OpenTofu + Ansible | runner uzima probni job; nijedan korak nije rađen rukom |
| 3 | Dev stroj — CX23, volumen, vatrozid, DNS | OpenTofu | plan bez ijednog brisanja, dva uzastopna plana isti ispis; destroy → apply potvrđuje vlasnik |
| 4 | Unutrašnjost — Docker, Caddy, compose, .env | Ansible | playbook dvaput zaredom: drugi put changed=0 |
| 5 | Cloudflare skelet — dva Pages projekta, jedna R2 kanta, prazno | OpenTofu | projekti postoje, nula deployeva tako je bilo do 20. 9.; flora-dev-si od tada ima domenu i deploye, flora-dev-hr je i dalje prazan |
| 6 | Katalog u bazi — 94 migracije, pa uvoz | bash | najveća nepoznanica uvoz za CI nije ni izmjeren ni skriptiran; postoji samo kao ručni postupak |
| 7 | Prvi build i upload na Pages | bash + CI | dev.plant-garden.hr ima 1 650 URL-ova u sitemapu i ispravan robots.txt |
| 8 | Kopija i vraćanje, prije nego išta postane važno | bash + Ansible | restore.sh na praznom stroju vrati dev okolinu — to trajanje je prvi pravi podatak za RTO produkcije |
Koraci 1–5 su slaganje poznatog; korak 6 je pravi rad. Uvoz kataloga je jedino mjesto gdje plan realno zapne, i zato je granica opsega: prompt 07a ide do koraka 5 i staje, 08a uzima katalog i prvi deploy, 09a kopije. Dev okolina se do koraka 5 može dići i bit će prazna ljuska — to je u redu i namjerno.
Ovo je inventar, ne prijedlog: svaki ključ ispod netko doista čita. Popis je izvučen iz src/env.ts, iz process.env poziva po src/, i iz .env.example datoteka. Dva neslaganja su pritom ispala na vidjelo — kartica na dnu.
Dopuna 22. 9.: dvije varijable više se ne upisuju rukom, jer su obje bile krive. FLORA_DEV_HOST i FLORA_DEV_KNOWN_HOSTS opisuju stroj koji tofu stvara i uništava, a stajale su ručno upisane u GitLabu. Na dan provjere FLORA_DEV_HOST je pokazivao na stroj koji ne postoji, a adresu iz FLORA_DEV_KNOWN_HOSTS Hetzner je u međuvremenu dao stroju flora-nexus — deploy po tom paru ili pukne, ili pokuca na tuđa vrata. Obje su bile točne kad su upisane: host key zapisan uz adresu je tvrdnja s rokom trajanja, jer se adrese recikliraju. Sada su gitlab_project_variable u dev stacku, pa im je vijek jednak vijeku stroja — apply ih napiše, destroy ih makne, i iza brisanja ne ostaje adresa mrtvog stroja u CI-ju. (flora-infra 0bb9159, 23. 9. još nepushan.)
Sve kao masked gdje je tajna, i protected gdje vrijedi samo za production. Protected varijabla se vidi isključivo jobovima na zaštićenoj grani — to je zamjena za protected environments, koji su Premium.
| Skupina | Ključevi | Tajna? | Po okolini |
|---|---|---|---|
| Baza flora-core |
DB_HOST · DB_PORT · DB_NAME · DB_SCHEMA · DB_USERNAME · DB_PASSWORD | DB_PASSWORD | da — dev, staging, production |
| Aplikacija | APP_ENV · PORT · DASHBOARD_PORT · ASSET_URL_PREFIX · STOREFRONT_URL_HR · STOREFRONT_URL_SI | ne | da |
| Pristup i potpisi | SUPERADMIN_USERNAME · SUPERADMIN_PASSWORD · COOKIE_SECRET · ORDER_NUMBER_SECRET · CACHE_INVALIDATION_SECRET | sve osim username | da — različite vrijednosti po okolini, nikad iste |
| Pošta | SMTP_HOST · SMTP_PORT · SMTP_USER · SMTP_PASSWORD · EMAIL_FROM_ADDRESS · EMAIL_LIVE | SMTP_PASSWORD | da — EMAIL_LIVE je prekidač koji na stagingu mora biti isključen |
| GLS | GLS_USERNAME · GLS_PASSWORD · GLS_CLIENT_NUMBER · GLS_COUNTRY · GLS_ENVIRONMENT · GLS_WEBSHOP_ENGINE | GLS_PASSWORD | da — GLS_ENVIRONMENT=test svugdje osim u produkciji |
| Ostalo | VIES_ENDPOINT · MAGIC_LOGIN_URL_HR · MAGIC_LOGIN_URL_SL · MAGIC_LOGIN_URL_EN | ne | da |
| Storefront isto za HR i SI |
PUBLIC_VENDURE_SHOP_API · PUBLIC_CHANNEL_TOKEN · PUBLIC_LOCALE · BUILD_MIN_PRODUCT_PAGES · BUILD_MIN_CATEGORY_PAGES | PUBLIC_CHANNEL_TOKEN ide u preglednik | da — API pokazuje na dev, staging ili produkciju |
| Ključ | Čemu | Tip |
|---|---|---|
| REBUILD_TRIGGER_TOKEN_HR REBUILD_TRIGGER_TOKEN_SI | Backend zove GitLab pipeline trigger na izmjenu sadržaja. Ne stoji u GitLabu nego u Vendureu — GitLab ga samo izdaje | masked |
| CLOUDFLARE_API_TOKEN CLOUDFLARE_ACCOUNT_ID | wrangler pages deploy | masked, protected za produkciju |
| BACKUP_S3_ENDPOINT · BACKUP_S3_BUCKET BACKUP_S3_ACCESS_KEY_ID · BACKUP_S3_SECRET_ACCESS_KEY | Kamo idu dumpovi baze. Veže se uz odluku iz roadmapa 41 — dok pohrana nije odlučena, restore-db nema kamo pisati | masked, protected |
| ANSIBLE_SSH_PRIVATE_KEY | Isporuka nexusa iz runnera umjesto s laptopa | tip File, protected |
| ANSIBLE_VAULT_PASSWORD | Otključava inventar i tajne nexusa — vidi odjeljak ispod | masked, protected |
Ne stvaraj ih: CI_REGISTRY, CI_REGISTRY_USER, CI_REGISTRY_PASSWORD, CI_JOB_TOKEN, CI_COMMIT_SHA, CI_PIPELINE_ID — GitLab ih daje sam i pipeline ih koristi kakvi jesu.
Dvije odvojene skupine, i to je važno: inventar opisuje strojeve, a nexus.env je ono što playbook zapiše na poslužitelj. Prvo nije tajna, drugo jest.
| Skupina | Varijable | Gdje je danas | Kamo u CI-ju |
|---|---|---|---|
| Spajanje | ansible_host · ansible_port · ansible_user · ansible_ssh_private_key_file | ansible/inventory.yml, u .gitignore — imenuje prave strojeve | ključ kao File varijabla; ostalo u inventar pod Vaultom |
| Nexus stroj | nexus_domain · nexus_root · nexus_port · nexus_timezone · nexus_tls_cert · nexus_tls_key · nginx_conf_dir · nginx_enabled_dir | isti inventar | inventar commitan pod Ansible Vaultom, lozinka Vaulta je CI varijabla |
| Izvještajne stranice | report_role · report_sites (site_path, source, cron_minute) · report_script_dir · report_state_dir · report_cron_minute | isti inventar | isto |
| Tajne nexusa 22 ključa |
NEXUS_SECRET_WOOCOMMERCE_MASTER · _HR · _SI · NEXUS_SECRET_VENDURE · NEXUS_DASHBOARD_PASSWORD · NEXUS_API_TOKEN · NEXUS_SMTP2GO_API_KEY + 15 putanja i adresa | deploy/nexus.env na poslužitelju; predložak je nexus.env.example | sedam tajnih kao masked CI varijable, ostalih 15 u inventar |
inventory.yml je danas u .gitignore jer imenuje prave strojeve. Da bi Ansible tekao iz runnera, negdje mora postojati. Dvije mogućnosti:
Prijedlog: Vault. Isti razlog zbog kojeg je sha-lock.json u repozitoriju, a ne u nečijoj glavi.
Dva neslaganja koja je ovaj popis otkrio, oba u flora-coreu:
1. .env.example:16 deklarira FROM_ADDRESS, a kod čita EMAIL_FROM_ADDRESS (src/env.ts:139). Tko postavi dokumentirano ime, ne postavi ništa — u pogon ide rezerva noreply@plant-garden.local. Nije CI posao, ali bi na produkciji značilo poštu s krive adrese.
2. CACHE_INVALIDATION_SECRET se čita i izvozi (src/env.ts:125 i :199), ali ga ne koristi nijedna druga datoteka. Postavka koja ništa ne radi — protiv konvencije #2. Ime sugerira da je bila zamišljena baš za ono što sad gradimo pod rebuild triggerom; vrijedi provjeriti prije nego se uvede novi ključ pokraj nje.
Nijedan skener danas ne radi. Odluka vlasnika, 18. 9.: ne koristi se nijedan GitLabov skener — svi su open source alati u vlastitim Docker imageima, zaključani po sha256 digestu, ne po tagu. Tag se može prepisati, digest ne može. Sve četiri slike ispod su povučene i pokrenute nad ovim repozitorijima; brojke su izlaz naredbe, ne procjena.
| Alat | Image, zaključan digestom | Što pokriva | Izmjereno danas |
|---|---|---|---|
| Trivy 0.74.0 Apache-2.0 |
aquasec/trivy@sha256: |
Tri posla jednim alatom: ovisnosti iz lockfilea, skeniranje gotove slike, i config za loše postavke | flora-core: 16 CVE — 8 HIGH, 6 MEDIUM, 2 LOW. flora-libs, HR, SI: 0 u sva tri |
| gitleaks v8.30.1 MIT |
ghcr.io/gitleaks/gitleaks@sha256: |
Tajne u cijeloj git povijesti, ne samo u zadnjem commitu | 5 od 6 repozitorija čisto. Jedan nalaz u www-plant-garden-si — lažan, vidi karticu ispod |
| hadolint 2.15.1 GPL-3.0 |
hadolint/hadolint@sha256: |
Dockerfile — loše prakse i sigurnosne zamke | flora-core/Dockerfile: 3 nalaza — 2 warning (DL4006, redci 25 i 46), 1 info |
| semgrep LGPL-2.1 |
semgrep/semgrep@sha256: |
SAST nad TypeScriptom. Isti motor koji GitLab vrti ispod svog SAST-a — bez omota | digest dohvaćen, alat nije pokrenut jedini od četiri koji nije izmjeren |
Pravilo generic-api-key, datoteka public/product-paths.json, commit 80734107 od 18. kolovoza. Nije tajna. Ta je datoteka generirana (scripts/generate-product-paths.mjs) i sadrži 1 538 unosa slug → URL; vrijednosti su putanje duge 77–88 znakova, a entropija 3,96 je ispod onoga što pravi ključ ima.
Zaključak za izvedbu: .gitleaks.toml s allowlistom mora nastati istog dana kad i job. Bez njega je job crven od prvog prolaza, a skener koji je uvijek crven ljudi prestanu gledati za tjedan dana. To je gore nego da ga nema.
Zamka zaključavanja digestom, koju vrijedi znati unaprijed: digest zamrzava motor, ne nužno bazu ranjivosti. Trivy svoju bazu povlači pri pokretanju, pa zaključan image i dalje nalazi nove CVE-ove — ondje je zamrzavanje čista dobit. Gitleaks nosi pravila u samoj slici, pa ona doista zastare: novi obrazac tajne neće se hvatati dok se digest ne podigne. Semgrep ovisi o tome vuku li se pravila mrežom ili su commitana uz repozitorij. Zato podizanje digesta mora biti zakazan posao, ne stvar sjećanja — isti Renovate koji gleda npm ovisnosti gleda i digeste u .gitlab-ci.yml.
npm audit nad flora-coreom prijavljuje 18 pogođenih paketa (13 high), Trivy 16 CVE-ova — različito broje isto stanje. Ali samo su tri ovisnosti direktne: @vendure/core 3.7.3, @vendure/asset-server-plugin i concurrently. Ostalo ulazi kroz Vendureovo stablo — @apollo/server (izvan podrške od 26. siječnja 2026.), @nestjs/*, lodash, ws, multer, sharp, uuid, shell-quote.
svaki repozitorij · na MR, uz gate
flora-core · dodatno, uz gradnju slike
sync-nexus dobiva samo gitleaks: nema composer, nema framework, nema nijednu vanjsku ovisnost (repos.json, gateNote), pa nema što skenirati.
Poredano po tome što prvo ugrize. Ništa od ovoga nije izgrađeno.
| # | Što | Zašto sad, s brojkom |
|---|---|---|
| 1 | Smoke test nakon produkcijskog deploya | Nacrt ima smoke na stagingu, ali ništa ne provjerava objavljeni sajt. Deploy koji prođe, a sajt ne radi, danas nitko ne bi primijetio. |
| 2 | Tko sazna kad pipeline padne | Danas nitko. Noćni e2e ili zakazani rebuild koji padne je nevidljiv. sync-nexus već ima uzbune — preslikati taj obrazac, ne izmišljati novi. |
| 3 | Vježba povratka unatrag | Backup se testira restoreom, ali puni put deploy → kvar → povratak nikad nije prošao. Kriterij faze 5 glasi „povratak izmjeren u minutama" i taj broj ne postoji. |
| 4 | Mapa .env → CI varijable | Popisano 18. 9. — odjeljak Varijable: 33 ključa u flora-coreu, po 5 u svakom storefrontu, 22 u sync-nexusu, 17 u Ansible inventaru. Ostaje razvrstati koja okolina treba koji. |
| 5 | Nadogradnja ovisnosti | Verzije su pinnane točno, bez ijednog ^ — izvrsno za ponovljivost, ali ništa se nikad ne nadogradi samo. Cijena je gore: 18 ranjivosti i Apollo Server v4 izvan podrške od siječnja. Renovate kao zakazani pipeline je Free. |
| 6 | Gate za flora-base | Drži sha-lock.json i legal-layout.json — dvije datoteke o kojima ovisi svaki drugi gate — a nema nikakvu provjeru. Neispravan JSON ondje obara šest pipelinea odjednom. |
| 7 | Nadogradnja Vendurea | Zaseban posao, ne CI. Ali je jedina poluga za 15 od 18 ranjivosti, pa pripada na isti popis. |
| Stavka | Stanje |
|---|---|
| Kako @flora/client putuje | otvoreno Prijedlog: ostaje file: na susjeda, a sha-lock.json na production grani je ono što pinna verzije. Registry i submodul su odbačeni jer job ionako mora checkoutati četiri repozitorija, pa ne uklanjaju ništa — a registry uz to gubi hvatanje drifta. Ne blokira fazu 1. |
| GitLab tier | riješeno Free, potvrđeno od vlasnika 18. 9. Cijeli nacrt je pisan da stane u Free — što je unutra, što nije i čime se zamjenjuje, stoji u poglavlju iznad. Nije provjereno u samom GitLabu (nema glab CLI-ja ni tokena) nego u dokumentaciji. |
| Trajanje e2e suitea | nije mjereno Sastav suitea jest (poglavlje iznad), trajanje nije — traži tri servera na vlasnikovim portovima, a dva su u trenutku mjerenja bila ugašena. Mjeri ga sesija koja e2e preseli u flora-infra. |
| www-plant-garden-hr | nije izrezan Mjereno je na snimci koraka 1 (4 commita). Kad pravi rez dođe, brojke treba ponoviti. |
| Cijene paketa | zastarjelo Brojke iz infrastructure-options.md nisu ponovno provjerene kod davatelja i ovdje se ne citiraju. |
Sigurnosne kopije u pravom smislu ne postoje. Ono što postoji nusprodukt je isporuke: Ansible prije svake izmjene povuče arhivu onoga što je na nexus poslužitelju i ostavi je na ovom računalu. Baza i slike nemaju ništa.
| Što | Kopija | Gdje stoji |
|---|---|---|
| Baza trgovine | ne postoji | Razvojna je u Docker volumenu na ovom računalu. Produkcijska ne postoji. |
| Slike kataloga — 444 MB, 9.037 datoteka 18. 9. · bilo 9.036 | ne postoji | flora-core/static/assets, u flora-core/.gitignore:17 — dakle ni u repozitoriju ni na GitLabu. |
| Kod i povijest | postoji | GitLab, ali se gura ručno i u naletima — kopija je stara koliko i zadnji push. Od 17. 9. to je deset repozitorija, ukupno 40 MB .git-a; devet ih ima remote, www-plant-garden-hr nije pushan. |
| Nexus aplikacija | djelomično | flora-base/sync-nexus/backups — 1,4 MB, arhive prije isporuke, samo na ovom računalu. |
| WordPress plugin na masteru | nije prešao rez | Plugin je u flora-core/wp-venture-sync/, ali mapa backups ondje ne postoji (18. 9.). Tih 200 KB stoji u zamrznutom new-startu, pod starom putanjom samples/wp-venture-sync/backups. |
| Nexus baza | ne postoji | Na kontrolnom poslužitelju; noćni posao briše ono što je prošlo retenciju, ali ništa je ne kopira drugdje. |
Zahtjev je zapisan i nije mali: najveći prihvatljivi prekid je ≤ 1 sat (infrastructure-options.md). Paket B, koji je ondje preporučen, to postiže odvajanjem baze i oporavkom na točku u vremenu (PITR) — ali dok paket nije izabran, ništa od toga ne postoji.
| Podatak | Koliko smije nedostajati | Način |
|---|---|---|
| Baza trgovine — narudžbe, kupci, zalihe, postavke | minute | Neprekidno arhiviranje zapisa (PITR) uz dnevnu punu kopiju izvan poslužitelja. |
| Slike kataloga | sat | Trajna pohrana izvan slike kontejnera (roadmap 41) s vlastitom replikacijom — tek onda kopija ima smisla. |
| PDF-ovi adresnica | dan | Nastaju iz GLS-a i imaju retenciju po kanalu; kopija ide samo dok traje rok čuvanja. |
| Pravni dokumenti | bez gubitka | Već su u gitu s otiskom SHA-256 i grade se pri buildu — kopija je repozitorij. |
| Nexus baza | dan | Dnevni ispis baze uz postojeći noćni posao retencije, povučen s poslužitelja. |
Netestirana kopija nije kopija. Uvjet iz roadmapa nije „postoji backup" nego „sve se može uništiti i vratiti u sat vremena, dokazano vježbom". Dok vježba ne prođe i ne zapiše se izmjereno vrijeme, ova stranica ne smije pisati da je riješeno.