MCP je standard, ale prihlaseni k nemu ne. Oficialni specifikace stoji na
OAuth 2.1 a objevovani autorizacniho serveru pres .well-known. EasyWeb
(Centaur) ma prihlaseni vlastni: POST /login s HTTP Basic vrati trojici tokenu
a obnovuje se vlastnimi endpointy. Zadny OAuth, zadne .well-known, jina verze
protokolu, zadne SSE ani hlavicka sezeni.
Proto dve sluzby, ne jedna s prepinacem: firma pri zakladani konektoru
vyplnuje neco jineho. U obecne ID a tajemstvi aplikace nebo hotovy token,
u EasyWebu jmeno, heslo a nazev zarizeni. Slucovat to by znamenalo formular,
kde je pulka poli vzdycky k nicemu, a hadani, ktera pulka to prave je.
Obecna sluzba zustava plnohodnotna. Vlastni server je duvod pridat sluzbu, ne
duvod zavrit dvere ostatnim.
Pribylo:
- src/mcp/dialect.ts - rozdily obou serveru na jednom miste: prihlaseni, verze
protokolu, jestli se prijima SSE a jestli se posila Mcp-Session-Id. Rozesete
po klientovi by u kazdeho dalsiho serveru pribyl dalsi if na jinem miste
- sluzba MCP EasyWeb: adresa, jmeno, heslo, nazev a otisk zarizeni.
Prihlasovaci adresy si portal odvodi sam, otisk doplni z ID konektoru
- hotovy token u obecne sluzby. Rada verejnych serveru nic jineho nenabizi
- objevovani pres WWW-Authenticate, coz specifikace ma jako povinnou cestu.
Pouziva se az kdyz obvykla mista selzou, stoji to volani navic
- zivotnost z tela tokenu: kdyz server expires_in ani datum neposle, cte se
exp z JWT. Presne pripad EasyWebu
- strankovani nastroju: nastroj s parametrem cursor dostane v builderu prepinac
Nacist vsechny stranky. Kurzor je hodnota z odpovedi, takze v dobe stavby
stromu ho nikdo nezna a nejde ho vyplnit dopredu. Krok pak vraci navic items,
pages, pageCount a truncated. Strop je 20 stranek
Opraveno: prihlaseni driv zkousela password grant a HTTP Basic proti hlavnimu
endpointu. Prvni OAuth 2.1 zrusil, druhe neni nikde ve specifikaci a u EasyWebu
by stejne neproslo - ten chce Basic na /login, ne na /mcp.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Predchozi verze chtela po uzivateli token. Spatne zadani: zakaznik dostane ke
svemu serveru adresu, jmeno a heslo. Token nedostane a nema jak ho ziskat -
vyda ho az autorizacni server a ma omezenou zivotnost. Obstarat ho, hlidat
platnost a vcas ho obnovit je prace portalu.
Novy src/mcp/auth.ts:
- kde se prihlasit se zjisti od serveru pres
/.well-known/oauth-protected-resource a metadata autorizacniho serveru.
Rucni pole na adresu je jen zaloha pro servery, ktere metadata nemaji
- cim se zkusi v poradi client_credentials a password. Ktere z toho firma
dostala, se z udaju samych poznat neda a nutit ji to vybirat by znamenalo
ptat se na neco, co nevi
- bez autorizacniho serveru se posle HTTP Basic. Mensi servery zadny OAuth
nemaji a jmeno s heslem je u nich presne tohle
- zivotnost urcuje server: token se vymeni minutu pred vyprsenim, obnovi se
pres refresh_token, kdyz ho server vydal, a kdyz expires_in chybi, pocita se
s peti minutami, tedy odhaduje se dolu
- odmitnuty token (401 na token, ktery jsme meli za platny) se zahodi a volani
se zopakuje jednou. Podruhe uz ne, to uz nejsou platne udaje
Token se drzi jen v pameti. Je kratkodoby, po restartu se o novy rekne znovu,
a ulozit ho by znamenalo vsechna rizika ulozeni bez jakekoliv vyhody. Kes drzi
otisk udaju, takze zmena hesla ulozeny token zneplatni.
Udaje konektoru jsou ted adresa, jmeno, heslo a dve nepovinna pole (adresa pro
prihlaseni a rozsah opravneni). Ani jedno nemiri do hlavicky - Authorization se
pocita az pri volani z toho, co vydal autorizacni server.
Zpusob prihlaseni se pise do hlasky u konektoru: uzivatel vyplnil jmeno a heslo
a ma vedet, jak s nimi portal nalozil, nez zacne hledat chybu jinde.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Firma si zalozi napojeni na svuj MCP server, stiskne Nacist nastroje a jeho
nastroje se objevi v builderu jako kroky automatizace vcetne toho, jake
promenne prijimaji a jake vraceji.
Pribylo:
- sluzba `mcp` - jedina v katalogu bez pevnych operaci, rekne je az server.
Udaje: adresa serveru, token nebo klic v X-API-Key
- POST /connectors/:id/mcp/tools - zepta se serveru na tools/list a ulozi
vysledek. Je to zaroven overeni konektoru, proto u MCP neni tlacitko Overit
- src/mcp/client.ts - handshake, sezeni z hlavicky odpovedi, odpoved jako JSON
i jako SSE stream, strankovani nastroju, nic z toho nevyhazuje vyjimku
- src/mcp/schema.ts - ze schematu vzniknou pole kroku a zpatky se z vyplnenych
retezcu udelaji argumenty ve spravnych typech. Ten druhy smer je ten
podstatny: server ceka {"limit": 10}, ne {"limit": "10"}
- src/data/mcpTools.ts - nastroje v katalogu, kes nad tim, co je u konektoru
- sloupec `mcp` u konektoru (migrace 004). Bez ulozeni by po restartu zmizely
z katalogu kroky, ktere uzivatel uz ma ve stromech
- vnitrni krok runMcpTool - jedna obsluha pro vsechny nastroje vsech serveru
Rozhodnuti:
- nastroj patri firme, ne katalogu. serviceCatalog(tenantId) bez firmy nevrati
zadny, takze zapomenuty argument znamena "nic", ne "vsechno"
- ID operace nese ID konektoru (tool:<konektor>:<nastroj>), protoze firma muze
mit dva servery a na obou nastroj `search`
- krok se neopakuje, MCP nema idempotencni klic
- chyba nemaze nastroje, vypadek serveru nesmi vymazat kroky z automatizaci
- servery se pri startu neobvolavaji, jeden nedostupny by shodil katalog vsem
Dokumentace: prepsany 24-mcp-konektory.md na skutecny stav, novy
00-pro-programatory.md (rozcestnik, model ctyr pojmu, pravidla, ktera plati
vsude, co je krehke), doplnene 01, 12 a 99.
Mimochodem opraveno: setStatus v connectors/postgres.ts melo v RETURNING
doslovny retezec ${COLUMNS} misto dosazeni, a dva odstavce v dokumentu 12 byly
dvakrat.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Prvni verze byla postavena spatne. Davala MCP nastroje do vyberu kroku, tedy
nastroj vybiral clovek a vyplnil mu pevna pole. Tak MCP nedava nic navic proti
HTTP konektoru, ktery uz mame - je to protokol pro modely, kde si nastroj
vybira model podle toho, co je zrovna potreba. Byla to zamena kategorie.
Spravne: MCP konektor neni zdroj kroku, je to schopnost, kterou dostane krok
s modelem. V builderu se objevi krok "Nechat model splnit ukol" a v nem se
zaskrtne, ktera napojeni smi pouzit. Krok vraci vysledek plus seznam toho, co
model opravdu zavolal - bez nej je to cerna skrinka a do provozu to nepatri.
Dokument popisuje dve cesty, lisi se tim kudy tece token zakaznika:
- predat server modelu (OpenAI ho zavola sam) - malo prace, ale token jde do
OpenAI, server musi byt dostupny z jeho site a volani nejdou pres nas log
- byt MCP klientem my - vic prace, ale token zustava u nas, plati nase stropy,
redakce i seznamy povolenych IP, a funguje to s jakymkoliv modelem
Doporucena je druha. Tvar tool objektu overen proti dokumentaci OpenAI
(type, server_label, server_url, headers, authorization, allowed_tools,
require_approval).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Neni to naprogramovane, je to navrh: documentation/24-mcp-konektory.md.
Podstata problemu: MCP server je katalog operaci, ktery se zepta az za behu,
kdezto nas katalog je znamy pri prekladu. Retezec service.actions ->
findOperation -> scriptIdFor se dnes cely pta statickeho katalogu, takze by
slo pouzit jen nastroj, ktery uz nekdo predem zapsal do kodu - presny opak
toho, o co jde.
Navrh: jedna sluzba mcp a kazdy server jako konektor pod ni. Nastroje se
doplni do katalogu pres withRuntimeOptions, tedy tim samym zpusobem, jakym uz
se doplnuji resitele, skupiny a typy ticketu. Schema vstupu se prevede
z JSON Schema na OperationField, co se neprevede skonci jako json.
Jen Streamable HTTP, ne stdio: stdio by znamenalo pustit zakaznikuv program
uvnitr naseho containeru. Adresa projde stejnou kontrolou vnitrni site jako
HTTP a SMTP. Idempotence u MCP nefunguje, proto by se krok neopakoval sam.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Odstranena sluzba AI zpracovani textu. Delala totez co OpenAI, jen jinymi
slovy, a dva zpusoby jak udelat jednu vec znamenaji jen otazku, ktery pouzit.
Ukazkove stromy a stopy, ktere na ni odkazovaly, miri na openai.chat.
- nova kategorie Transformace dat. Driv byly mezi obecnymi sluzbami vedle
webhooku a pauzy; ve chvili kdy jich je vic nez tri, hleda je clovek jako
skupinu, ne mezi spoustecemi.
- skript transform.to-xml: zapise objekt jako XML vcetne escapovani. Cizi
systemy si XML porad rikaji casto (ISDOC, EDI, banky, statni sprava)
a bez toho se sestavuje retezcem v HTTP kroku, kde se na escapovani
zapomene pokazde. Klice, ktere nejsou platny nazev prvku, se prejmenuji
a rekne se to nahlas; klic zacinajici cislici dostane podtrzitko misto
odriznuti, jinak by 2024 a 2025 byly totez.
- navrh cele skupiny transformaci v 13-transformace-dat.md: formaty, pole
a hodnoty, seznamy, text a kontrola, vcetne toho co v seznamu zamerne neni.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sluzba byla v katalogu bez pristupovych udaju a s appId 'ai-text', tedy
mirila na aplikaci, ktera neexistuje. Konektor nemel co vyplnit a krok nemel
co vykonat.
- sluzba stoji na api.openai.com/v1 stejne jako OpenAI, overeni GET /models
- model se bere z konektoru (ctx.config.model), ne z kazdeho kroku zvlast
- skripty ai-text.classify, ai-text.extract, ai-text.generate
Proc to neni jedna sluzba s OpenAI: tam se posila volny dotaz a vraci volny
text. Tady je uloha pevna a vystup taky - category a confidence, objekt
s popsanymi poli, text s poctem slov. Na to jde ve strome navazat podminkou,
na volny text ne.
Zjisteno u toho a zapsane do 21-realne-sluzby.md: proxy vraci na neznamou
aplikaci 200 s prazdnym telem, takze overeni konektoru u sluzby bez
verifyPath projde i tam, kde nic nebezi. Zatim neopraveno.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Prepinani jazyku (web/src/i18n/). Vybrany jazyk je jedna hodnota pro celou
aplikaci drzena mimo React - tentyz vzorec jako u vybrane firmy, a ze
stejneho duvodu: musi byt dostupny i mimo komponenty a prepnuti nesmi byt
stav jedne stranky. Volba prezije obnoveni a prepisuje <html lang>.
Anglicky slovnik je Partial: co v nem neni, spadne na cestinu. Kdyby musel
byt uplny, znamenal by kazdy novy cesky text rozbity build, dokud ho nekdo
neprelozi - a vysledkem by bylo, ze se texty nepridavaji. Prelozena je
hlavicka, hero vcetne snimku, pas s logy, cisla, zaverecna vyzva a cast
paticky; zbytek je mechanicky a popsany v 23-jazyky.md.
Snimek v heru vypada jako portal doopravdy. Byl svetly a ukazoval obrazovku,
ktera v produktu neexistuje. Ted ma tmavy podklad, tytez zalozky vcetne
prepinace firmy, tytez sloupce tabulky i tytez odznaky stavu jako stranka
Tickety. Kdyz se navstevnik prihlasi a uvidi neco jineho, prvni dojem
z produktu je, ze web lhal.
Jmeno znacky si sklada usePageMeta, ne stranky. Bylo napsane natvrdo ve
dvaceti ctyrech titulcich, takze prejmenovani zmenilo brand.ts a titulky ne:
hlavicka rikala nove jmeno a zalozka v prohlizeci stare.
Opraveno: odkaz "Prohlednout si to" vedl na koren domeny misto
/apps/<app-id>/sluzby, protoze to byl <a href> a ne Link z routeru. Byl to
jediny takovy odkaz v celem webu.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Nasazena aplikace o dva commity pozadu funguje a vypada skoro stejne jako
nova. Bez oznaceni buildu se to nepozna a hleda se chyba tam, kde zadna
neni - prave to nas dnes stalo pul hodiny hadani, proc je na produkci
porad stary vzhled.
- oznaceni buildu v pate postranniho menu, pod tlacitkem Odhlasit se:
verze, cas buildu a cislo commitu. Text jde oznacit jednim kliknutim.
- totez jako <meta name="app-build"> v hlavicce stranky. V JS balicku uz to
je, ale ten se musi stahnout a rozbalit; meta znacka je videt na jeden
curl bez prihlaseni, a presne to clovek potrebuje pri zjistovani, co bezi
na produkci.
- hodnoty dosazuje Vite pri buildu (define a plugin build-stamp), takze
neplati pro repozitar, ale pro ten konkretni balicek.
- .dockerignore uz nevylucuje .git/. Build z nej cte cislo commitu; bez nej
by se poznal jen cas buildu, ne co v nem je. Do vysledneho image se .git
nedostane, ten se sklada z ciste zakladni image a kopiruje se do nej jen
dist, scripts a migrace.
Ve vyvoji se misto casu pise "vyvoj" - cas buildu by tam byl pokazde jiny
a nikomu by nic nerekl.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Prevzeti vizualniho smeru z predlohy. Web i portal stoji na tychz tokenech,
takze zmena hodnot v index.css se projevila naraz na obojim - portal je
zeleny, aniz by se sahlo na jedinou jeho komponentu.
Znacka a tokeny:
- akcent z cyanove na zelenou #B7FF3C, podklad z modrocerne na zelenocernou
#0F1411. Seda dostala zelenou prichut, aby vedle podkladu nepusobila spinave.
- druhy akcent a text-gradient se nemazaly, jen presmerovaly do zelene rady.
Pouziva je 341 mist a prepsat je vsechna kvuli barve by byla zbytecne velka
zmena; fialova z nich zmizela tak jako tak.
- stav "v poradku" je nove modrozeleny. Akcent je zeleny a dve zelene na jedne
obrazovce, kazda o necem jinem, nikdo nerozlisi.
- pismo Archivo na nadpisy, IBM Plex Sans na text, IBM Plex Mono na cisla a ID.
Nadpisy maji tesny proklad, ktery Inter neumel bez trikareni.
- znak je plocha bomba misto pismene v gradientnim ctverci. LogoMark je
samostatny export, aby sel pouzit i uvnitr snimku v heru. Novy favicon.
- primarni tlacitko je plna zelena misto prechodu ze dvou barev.
Hero a navigace:
- prokladany nadtitulek misto odznaku, dvouslovne radky nadpisu, jedno
primarni tlacitko plus textovy odkaz, ctyri slovesa misto tri odrazek
a snimek portalu vybihajici z prave hrany.
- navigace je pri levem okraji a bez pilulky za aktivni polozkou. Jedina
pilulka na liste je primarni akce.
Opraveno u toho:
- zare v heru je pozadi sekce, ne vrstva pod ni. Prekryv se zapornym
z-indexem se schoval za podklad stranky a nebyl videt vubec.
- presah snimku se pocita ze sirky kontejneru. Pevna hodnota ve vw stacila
na notebooku, ale nad 1600 px se snimek vesel do postranni mezery
a prestal byt oriznuty.
Zamerne nezmeneno: klice v prohlizeci (automia.token, automia.tenant), ID
firmy tnt_automia, ID aplikace csbot-prototype a prihlasovaci e-maily
v ukazkovych datech. Vypadaji jako jmeno, ale jsou to identifikatory.
Popis, jak znacka funguje v kodu, je v documentation/22-znacka-a-design.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Dodelavka. Tvrzeni "prepinac plati pro cely system" nebylo pravdive:
useApiQuery se na zmenu firmy zeptat umel, ale komponenty volajici
apiFetch primo - EntityAdmin, a tim cele Nastaveni a zalozky Resitele
a Skupiny v Lidech - o zmene nevedely a zustala na nich data predchozi
firmy. Presne ta vada, kvuli ktere se to delalo, jen o patro vedle.
- obsah dashboardu ma key podle vybrane firmy, takze prepnuti prestavi
cely strom komponent. Zadny dotaz nemuze zustat stary, protoze zadna
stara komponenta nezustane. Doplnovat zavislost do kazde komponenty
zvlast je totez zapomenuti, jen o patro niz.
- prepinac je v sidebaru nad navigaci, ne v hlavicce, a vypada jako
ovladaci prvek: ram, ikona firmy, sipka. Predchozi verze mela pruhledny
ram a splyvala s popiskem.
- v hlavicce zustal jen nazev aktivni firmy. Driv tam stalo natvrdo
tenants[0], takze po prepnuti ukazovala porad prvni firmu ze seznamu.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
permissionsOf(user) scitalo role pres vsechna clenstvi, takze kdo byl
spravce v jedne firme, jednal jako spravce ve vsech, kam patril. Uzivatel
ve dvou firmach je vzacny, dusledek ne.
- firma je povinny argument u permissionsOf, hasPermission i hasAnyPermission.
Zamerne povinny: kdyby byl nepovinny, prvni volani bez nej by tise vratilo
vsechna prava. Prekladac tim rovnou nasel vsech ctrnact mist, ktera se ptaji.
- kazde misto se pta za tu spravnou firmu: akce nad ticketem za firmu toho
ticketu, uprava zaznamu za firmu toho zaznamu, zalozky a helpdesk za firmu,
kterou ma clovek prepnutou
- cizi firemni role se ignoruje, i kdyby na ni clenstvi odkazovalo. Jinak by
stacilo pripsat si ji do clenstvi v jine firme a prava by se prenesla.
- cache prav ma v klici firmu
- readScope cte z jedne firmy, te prepnute, ne ze vsech. V nastaveni se driv
michaly typy ticketu a role napric firmami bez ohledu na vyber.
Opraveno u toho: systemove role se pri startu srovnaji s kodem
(syncSystemRoles). Vychozi sada se pouzije jen do prazdneho uloziste, takze
nove pravo v katalogu se k uz bezici instalaci nikdy nedostalo - ticket.create
ani obe prava helpdesku by spravci firmy nenabehla a zalozka Helpdesk by se
neobjevila. Meni se jen prava, jen u nasich roli, nazvu se to netyka.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Prepinac byl stav uvnitr stranky Prehled, takze prepnuti na LogiTrans
zmenilo prehled a nic jineho - ostatni stranky volaly API bez tenantId
a server sahnul po vychozi firme. V Lidech tak zustali lide Automie.
- vybrana firma je jedna hodnota pro cely dashboard (web/src/lib/tenant.ts)
a prezije obnoveni stranky
- tenantId doplnuje apiFetch, ne jednotlive stranky. Kdyz si to mela
pridavat kazda stranka sama, vetsina na to zapomnela - a prave to byla
ta chyba. Nedoplnuje se, kdyz cesta uz firmu nese, u scope=all
a mimo /api/dashboard.
- useApiQuery se pri prepnuti zepta znovu, jinak by na strance zustala
cisla predchozi firmy
- prepinac je v hlavicce nad obsahem a nahradil text, ktery ukazoval
natvrdo prvni firmu ze seznamu bez ohledu na vyber
- ulozena firma, do ktere uzivatel uz nepatri, spadne na vychozi
Zjisteno u toho a zapsane do 07-firmy-a-prava.md: prava se scitaji pres
vsechny firmy, neprepocitavaji se podle vybrane. Data se tim neprolomi,
server je filtruje dal, ale tlacitka ukazuji vic, nez by mela.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Katalog srovnany s tim, co opravdu bezi na services.csbot.cz/apps:
trinact sluzeb dostalo pristupove udaje a levne cteci overeni, opravena
appId, ktera nikam nevedla (ppl, microsoft365, transcription), a GA4,
Search Console, Google Ads i Sklik ted stoji na aplikaci analytics,
kazda s vlastnimi udaji. Nove sluzby SAP Business One, Google Workspace
a Meta Ads. K tomu 23 skriptu, ktere s nimi opravdu neco delaji.
OpenAI jako prvni sluzba, ktera nebezi u nas: Service.baseUrl s absolutni
adresou, prepis pres <SLUZBA>_BASE_URL nebo adresu u konektoru, predpona
hlavicky u pole udaju (uzivatel vlepi holy klic, Bearer dopise runtime).
Dotaz na model, nahrani souboru, otazka nad souborem, prepis zvuku.
Skript umi odeslat soubor pres ctx.http.postForm (multipart, obsah Base64).
Sluzba E-mail pres SMTP. Neni to skript, ale vnitrni krok - SMTP neni HTTP.
Konektor nese schranku firmy, krok ma HTML telo, ve kterem se dosazene
hodnoty escapuji (znacky autora sablony jsou zamer, ostre zavorky od
zakaznika ne). Overeni konektoru se prihlasi na server a nic neodesle.
Helpdesk: Ticket.helpdeskSourceId drzi firmu, ktera pozadavek poslala,
vlastnikem zustava ta, ktera ho resi - jinak by ho resitel nemel ve sve
fronte. Komu pozadavek pripadne, urcuje Tenant.helpdeskProviderId.
Zadavatel vidi jen svoje pozadavky a smi k nim pripsat komentar.
Opravy v portalu:
- hlasky o ulozisti a odchozi IP vidi jen spravce platformy
- typ ticketu se v automatizaci vybira ze seznamu firmy, nebo dosadi z dat
- stav ticketu je otevreny naseptavac, ne ciselnik
- ticket jde zalozit rucne, zakaznik u nej neni povinny
- kanal se prejmenoval a parametry u webhooku jsou oznacene jako nepovinne
- srovnane markdown tabulky v cele dokumentaci
Co z teto davky jeste neni: prepinac firmy je porad jen stav uvnitr stranky
Prehled, takze se prepnuti neprojevi v Lidech ani jinde.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Echo sluzba na internetu odpovi verejnou adresu. Jenze volani na vlastni
domenu se otaci zpatky na tentyz stroj a reverse proxy pak vidi neco jineho,
typicky adresu docker bridge. A prave tu porovnava seznam povolenych IP
u sluzeb za toutez proxy, takze verejna adresa muze byt povolena a volani
z containeru presto skonci na 403.
Zmeri se to tak, ze portal zavola svoji vlastni verejnou adresu
(PUBLIC_ORIGIN + ROOT_PATH + /whoami) a precte si, jak k nemu volani doslo.
Kruh sam pres sebe, ale nic jineho tuhle adresu nezjisti: mezi container
a server se tim dostane ta sama proxy, kterou prochazi volani na sousedni
sluzby.
Novy /api/whoami je zamerne bez prihlaseni. Vraci volajicimu jeho vlastni
adresu, tedy nic, co by uz nevedel, stejne jako kterakoliv echo sluzba.
Obe mereni bezi naraz a jsou videt na strance Konektory vedle sebe.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Kdyz cizi sluzba odmitne pristup, prvni otazka je, z jake adresy se vlastne
vola. Z containeru to videt neni, vidi to az protistrana, takze se zepta echo
sluzby podle EGRESS_IP_URL a vysledek se drzi v pameti po EGRESS_IP_TTL_MS.
Prazdna EGRESS_IP_URL funkci vypne, prepsat ji jde na vlastni echo pod svou
domenou.
Adresa je natvrdo na strance Konektory a u kazdeho odmitnuteho overeni v logu.
Pripojuje se jen u 401 a 403 - jinde nema co rict a nestoji za volani ven.
Neni to tajemstvi: kazda volana sluzba tuhle adresu stejne vidi.
Endpoint egress-ip je registrovany pred GET /:id, jinak by ho router vzal
jako id konektoru.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Dialog se vykresloval uvnitr karty konektoru misto pres obrazovku. Samo
position: fixed nestaci: rodic s backdrop-filter (nase .glass, tedy skoro
kazdy panel a karta) je pro fixed potomka containing block. Modal proto jde
portalem do document.body. Tykalo se to vsech dialogu, videt to bylo az
u Logu, ktere jsou v male karte.
K chybe se zapisuji vybrane hlavicky odpovedi: server, via, content-type,
www-authenticate, retry-after, x-request-id, date. Rikaji, kdo odpoved vydal.
Server: Kestrel je sama aplikace, Via: 1.1 Caddy proxy pred ni. U 403 od proxy
byva telo prazdne a bez hlavicek by nezbylo vubec nic. Allowlist, ne vsechno:
Set-Cookie a podobne do zaznamu nepatri.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Duvod od sluzby jde primo do hlasky: z tela odpovedi se vytahne detail,
error_description, message, title, error i seznam missingHeaders. Retezec,
ktery vypada jako JSON, se rozbaluje dal - sluzba iDoklad presne takhle
predava telo od iDokladu samotneho. Kdyz sluzba nenapsala nic, rekne se to.
401 a 403 uz nejsou jedna hlaska. 401 = udaje sluzba dostala a neuznala je.
403 = tvar udaju je v poradku, zakazuje se samo volani.
V kazde hlasce je cela adresa vcetne serveru (ScriptRequestInfo.url),
bez query - v query muze byt tajemstvi. Zaklad adresy je z konfigurace
a konektor ho smi prepsat, takze se neda odvodit z toho, kde je nasazeny
portal. Adresa je videt i na karte konektoru a v odpovedi na test, i kdyz
overeni projde.
Tlacitko Logy na karte konektoru a historie poslednich peti overeni.
Odpoved sluzby dosud existovala jen v odpovedi na test, tedy do prekresleni
stranky, a v logu containeru. Do logu containeru se nikdo divat nechodi.
Zaznam se uklada i pri uspechu, jinak by neslo poznat, jestli konektor nesel
nikdy, nebo prestal jit ve chvili, kdy nekdo sahnul na udaje.
Migrace 003_connector_checks.sql, endpoint GET /connectors/:id/checks.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bezny postup nesel poskladat z kroku: najdi podle ICO, kdyz neni zkus mail,
kdyz porad neni zaloz - ID dava jednou jedna vetev a jednou druha, ale
rozsah vystupy z vetvi za podminku nepoustel. Slucovat kvuli tomu hledani
a zakladani do jednoho kroku bylo obejiti nasi chyby, ne reseni.
- Vystup z vetve je za podminkou k dispozici, jen jako nepovinny. Ze muze
chybet, se neztratilo: builder to u pole ukaze a pri behu se dosadi
prazdno.
- Vystup se stejnym jmenem uz z nabidky nemaze ten starsi. Po druhem
hledani kontaktu zmizelo ID z prvniho, tedy to, co je v tu chvili
potreba. Odkaz se jmenem kroku je jednoznacny.
- Duplicitni jmena u vystupu kroku uz nejsou nedodelek. Konflikt zustava
mezi parametry spoustece, kde zadny prefix neni.
- Novy krok Zalozit kontakt. Nic nedohledava, hledani je vlastni krok.
Najit nebo zalozit zustava pro toho, komu staci jistota jednim krokem.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Klikaci pravidla jsou u peti poli rychlejsi, ale u modelu objednavky je jich
dvacet a v tom se necte. Vedle nich proto skript firmy: prevod z A do B
napsany v JS, jeden na zakaznika.
- Skripty se ukladaji do uloziste, ne na disk. Disk je uvnitr kontejneru
a redeploy ho vymaze.
- Krok Transformace dat - Vlastni skript. Vysledek jde dal jako krok.result.
- V logu ticketu je u kroku vstup i vystup. Prave to byl duvod, proc skript
nad pravidly vyhral.
- Zkouska bez ulozeni: v portalu se vlepi skutecne telo a hned je videt, co
z toho leze.
- Skripty jsou v zalozce Akce, vedle definic akci. Obojí je popis toho, co
aplikace ve firme umi, a spravuje to tentyz clovek.
Skript je ciste prevod hodnot: dostane input, vrati objekt. Nema require,
import, process, fetch ani console, bezi nejvys 2 s a vysledek se vejde do
256 kB. node:vm neni bezpecnostni hranice proti nekomu, kdo se chce dostat
ven - je to izolace proti nehode a proti zacykleni.
Pri zkousce se ukazalo, ze casovy limit nepokryval samotny beh: runInContext
jen vyrobil funkci a zavolat ji zvenku znamenalo, ze while (true) uvnitr
zablokovalo proces navzdy. Kod se ted vola uvnitr runInContext.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Odesilatel posila cely model. Objednavka ze Shoptetu ma zanoreni, ceny
v podobjektech a seznam polozek - a dosud sel napojit jen plochy seznam
skalarnich poli, takze items[] neslo pouzit vubec.
- Odkaz v sablone muze byt cesta: {{data.order.billingAddress.city}},
{{data.order.items[0].name}}, {{st_faktura.invoiceId}}. Overuje se prvni
cast odkazu, takze ploche odkazy funguji dal presne jako driv.
- Cele telo je v kontextu i v puvodnim tvaru. Deklarovane parametry maji
pri shode jmen prednost.
- Spoustec si pamatuje ukazku skutecneho tela. Server z ni odvodi model,
tedy seznam cest i s typy, a ten se v krocich klika misto opisovani.
Tlacitko doplni z hodnot v ukazce parametry pro podminky.
- Novy krok Pro kazdou polozku: projde seznam a za kazdou polozku vykona
vnoreny podstrom. Uvnitr je item a index, po skonceni krok.results se
seznamem vysledku. Kazdy vysledek nese i puvodni polozku - radek
objednavky potrebuje jak ID z CRM, tak mnozstvi z puvodnich dat.
Strop 200 polozek, mimo seznam krok selze s tim, co tam misto nej je.
- Prevod Za kazdou polozku seznamu v klikacim editoru mapovani. Engine ho
umel, sel ale napsat jen rucnim JSONem.
- Typograficke uvozovky a sipka z kodu pryc.
Overeno nad skutecnym modelem objednavky: 23 cest vcetne
data.order.items[].unitPrice.withoutVat, sablony s cestou i s indexem,
smycka nad dvema polozkami s posbiranymi vysledky.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Overeno proti serveru: tri shodne POSTy udelaly jednu udalost s pocitadlem 3,
ale krok se do logu porad zapsal trikrat. Zadani bylo, ze opakovani ma byt
informace, ne dalsi radek.
- Krok muze rict quiet a jeho radek se do logu ticketu nezapise.
- Ticket vznika uz s poslanym stavem. Predtim se zalozil s vychozim "Novy"
a hned se prepsal, takze v logu stalo "stav Novy -> completed" u ticketu,
ktery v tom stavu nikdy nebyl. Odtud i to "Novy" ve widgetu.
- Zaznam zmen: celkovy pocet behu se pocita od zavedeni historie po dnech,
puvodni citac se den ode dne nedelil a rozpocitat ho zpetne neni z ceho.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Nalezeno na bezicim serveru: TK-4946 mel 177 udalosti a 620 radku logu,
pritom se skoro nic nestalo. Zmereno proti fronte: ve stejnem okne vzniklo
presne tolik behu, kolik prislo udalosti (22 a 22), kazdy s jednim pokusem.
Fronta nenasobi nic, odesilatel poslal 177 POSTu. Nase vina byla, ze to
z historie neslo poznat.
- Data udalosti se ukladaji. Kdyz krok nema vlastni, ulozi se to, cim beh
zacal - u webhooku prijate telo. Prazdna udalost je horsi nez zadna.
- Shodna udalost se pocita (repeats, lastAt), nezaklada dalsi radek. Ticket
se pritom nemeni, takze duplikat nerozblika dashboard ani nespusti
automatizaci na zmenu ticketu. Zahodit ji nejde, jinak by nikdo nezjistil,
ze proti nam neco tluce.
- Zmeny se radi pod udalost, ktera je zpusobila, a u udalosti stoji jmeno
automatizace. Log se cte jako "prislo tohle -> zmenilo to tohle".
- Poznamka o stavu jen kdyz se stav zmenil. "z in-progress na in-progress"
u kazde zpravy byl zdroj tech 620 radku.
- runsToday konecne znamena dnes: behy po dnech, k tomu vcera a celkem.
Dosud to byl citac od zalozeni automatizace, jen se jmenoval "dnes".
Vedle toho prace, o kterou slo predtim:
- Prevzeti ticketu ze skupiny (POST /tickets/:id/claim) a krok Predat skupine
s prepinacem automatickeho prideleni nejvolnejsimu.
- Pozvanky do firmy: odkaz s nahodnym kodem, heslo si nastavi pozvany.
- Resitele, skupiny a pozvanky presunuty z Nastaveni do zalozky Lide, cleny
skupiny se vybiraji klikanim.
- Ctyri AI znaky, ktere zbyvaly v kodu, pryc.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`Ticket.closed` se nastavuje vyslovne. Predchozi verze ho odvozovala ze jmena
stavu (completed, vyreseno, ...), coz nikdo nechtel a hlavne to uhodne spatne
pokazde, kdyz si nekdo pojmenuje stavy po svem. `TicketType.closedStatuses`
zruseno, byla to tatáz obchazka o uroven vys.
Kroky `ticket/upsert` a `ticket/set-status` maji vstup Vyrizeny s trema stavy:
ano, ne, prazdne. Prazdne znamena nemenit - jinak by kazda zmena textu stavu
mimochodem otevrela vyrizeny ticket. `POST /tickets/:id/status` prijima
`closed` jako nepovinny bool a podminka ve strome se na nej muze zeptat.
Overeno 8 kontrolami: ticket se stavem completed neni automaticky vyrizeny,
dokud to nekdo nerekne. Automatizace s podminkou status = completed zabere na
ticketu, ktery do toho stavu prejde, ale na uz existujici tickety nesahne -
spousti ji udalost, ne stav.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ciselnik new/open/waiting/resolved je pryc. Tickety chodi z cizich aplikaci,
ktere maji svoje stavy - voicebot posila ringing a completed. Nutit je do nasi
ctverice znamenalo, ze u ticketu svitilo "Novy", i kdyz byl podle odesilatele
davno hotovy.
Misto nej priznak `closed`: fronta, vytizeni i statistiky potrebuji vedet, co
uz nikdo neresi, a z volneho retezce to poznat nejde. Nastavuje se sam podle
`TicketType.closedStatuses`, a kdyz je typ nema, podle bezneho pojmenovani
(vyreseno, hotovo, completed, closed).
`stage` zruseno. Byla to obchazka, jak dostat cizi stavy do ticketu, aniz by
se sahlo na ciselnik. Kdyz je stav volny, druhe pole na tutéz vec jen matlo.
Vyber stavu v detailu nabizi stavy typu, doporucene a ten, ktery ticket ma
prave ted, aby hodnota z cizi aplikace ze seznamu nezmizela. Filtr v seznamu
nabizi stavy, ktere v datech opravdu jsou.
Overeno 11 kontrolami: ticket z voicebota ma stav ringing, pak in-progress
a completed, completed se pozna jako hotovo a zmizi z fronty, filtr i widget
ukazuji tvoje stavy a rucne jde nastavit i "ceka na zpetne volani".
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bez databaze lezi data uvnitr containeru, takze redeploy je smaze a seed je
nasype znovu. Dokud nebude Postgres, resi se to trema vecmi:
Ukazkova data jen se SEED_DEMO=1. Automatizace, tickety a incidenty se uz po
kazdem nasazeni nevraci. Konfigurace se nasypava dal, bez ni je portal
nepouzitelny.
Token webhooku z WEBHOOK_TOKEN_TEST. Driv se pri kazdem nasazeni vygeneroval
novy, takze odesilatel musel prepisovat adresu ve svem kodu. Ted je token
v promenne aplikace: neni v gitu a adresa se nemeni.
Automatizace, ktera na instanci opravdu bezi, je v seedu. Je to provizorium,
ne cil - az data prezijou nasazeni, patri zpatky do dat.
Dal:
- `ticket/upsert` umi vsechna pole ticketu: zakaznik, kanal, odkaz na zdroj,
priorita, stitky, resitel, skupina a vlastni pole typu jako JSON. Zakaznik
a kanal se vyplnuji jen pri zalozeni, aby pozdejsi udalost s prazdnym
jmenem neprepsala, co uz tam je.
- Kroky ve strome jdou sbalit, vychozi je sbaleno. Sbaleny krok ukazuje, co
ma vyplneno, ne popis operace.
- Seskupovani widgetu podle faze a dva nove widgety: tickety podle stavu
a podle faze za tento mesic, obojí s proklikem na vyfiltrovany seznam.
- Faze se ukazuje jako stav. Driv byl videt jen nas ctyrprvkovy ciselnik,
coz u ticketu z cizi aplikace nedava smysl. Zivotni cyklus zustava vedle
jako drobny text, protoze se z nej pocitaji statistiky.
Overeno 18 kontrolami proti bezicimu serveru.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Opraveno: `ticket/assign` nemel vykonnou cast, takze krok "Prirad resiteli"
vzdycky selhal hlaskou "operace nema vykonnou cast". Doplnen jako vnitrni krok
vedle prirazeni nejvolnejsimu ze skupiny a prirazeni podle externiho ID.
Ukazkova automatizace "Smerovani ticketu na resitele" je vypnuta. Zapnuta
prebirala tickety, ktere uz nekomu patrily podle skutecne automatizace
zakaznika, a prepsat rucni nebo cizi rozhodnuti je to nejhorsi, co muze
automatizace udelat. Do udaju spoustece zaroven pribylo `assigned`
a `knownCustomer`, aby slo napsat podminku "uz je prirazeny, nesahej na to".
`ticket/upsert` prijima `status`: kdyz hodnota patri mezi nase ctyri stavy,
nastavi stav, jinak se ulozi jako faze. Cizi aplikace posila svoje stavy
hovoru a nas zivotni cyklus je pevny, protoze se z nej pocitaji statistiky.
Do shrnuti kroku se napise, co se stalo.
Overeno pripadem z provozu: callSid do externiho ID, voicebotId jako stitek,
status jako stav. Ctyri zpravy o trech hovorech daly tri tickety, filtr na
stitek vratil jen hovory daneho voicebota a widget je spocital vcetne
prokliku na vyfiltrovany seznam.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Webhook uz nic nevykonava v requestu. Zapise udalost do fronty a odpovi 202
do jednotek milisekund; strom vykona worker na pozadi. Za konektory nerucime,
takze cekat na cizi sluzbu v requestu znamena ztracet udalosti pri timeoutu.
Fronta ma opakovani s rostouci prodlevou (30 s, 2 min, 10 min, hodina),
spravedlive poradi po firmach (jedna firma s tisicem udalosti nezablokuje
ostatni), navrat zaseknutych behu po restartu a uklid hotovych. Marna chyba
se neopakuje - chybejici skript za minutu existovat nezacne.
Tri druhy spoustecu: push (webhook), vnitrni udalost (vznik a zmena ticketu)
a pull, tedy pravidelne dotazovani u sluzeb bez webhooku (posta, zpravy).
Planovac jen rekne "je cas", samotny dotaz je prvni krok stromu, takze ma
zaznam v logu a opakuje se pri chybe jako cokoliv jineho.
Kontrakt tela webhooku: kazdy parametr ma cestu (data.order.id,
errors.0.message), takze jde napojit i odesilatel s vnorenym modelem.
U adresy je metoda, ukazka tela a kopiruje se cela adresa vcetne domeny.
Vnitrni kroky, ktere sahaji do naseho uloziste: ticket/upsert (zaloz nebo
dopln podle externiho ID), assign-least-busy, assign-by-external, set-type,
set-stage, add-tags, set-status, incident/create, flow/pause a flow/log.
Faze ticketu jako treti osa vedle stavu a stitku. Stav je zivotni cyklus
a pocitaji se z nej statistiky, faze je workflow daneho typu a muze byt jen
jedna, takze se na ni da spolehnout v podmince.
ID z cizich aplikaci u resitele: voicebot posle voicebotId a ticket skonci
u toho, komu patri. Vazba je na jednom miste, ne v kazde automatizaci.
Kazda chyba zaklada incident se dvema urovnemi: impact cte klient a je
srozumitelny, detail cte admin a je v nem cely beh, ktery krok selhal, cele
hlaseni a data na vstupu. Detail vidi jen spravce platformy.
Ochrana proti smycce: automatizace navazana na zmenu ticketu ticket meni,
cimz se spousti znovu - pri vyvoji to server polozilo. Resi to oznaceni behu
pres AsyncLocalStorage a strop peti behu na jeden ticket za minutu.
Upozorneni pri prideleni prace vcetne cisla u zalozky Tickety. Zivy dashboard:
dlazdice nad nasimi daty na udalost, data z konektoru podle ttlSec s moznosti
vynutit nacteni znovu.
Opraveno: path a intervalSec u spoustece se pri ulozeni zahazovaly; nad
seznamem neslo pouzit contains, takze na stitky neslo postavit podminku;
novejsi vystup kroku ted prekryje starsi misto hlaseni konfliktu.
Overeno dvema scenari proti bezicimu serveru, 34 kontrol: firma se skladem,
expedici a IT, a hovory z voicebota (callSid do externiho ID, status do faze,
prirazeni podle voicebotId, tri zpravy = jeden ticket se tremi udalostmi).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Runtime: `src/runtime/executor.ts` jde krok po kroku, u podminky se vetvi,
do poli dosadi parametry, akci pusti pres runScript a vystupy pripise do
kontextu pro dalsi krok. Cely prubeh jde do logu ticketu vcetne toho, co
sluzba vratila. Pouzivaji ho obe cesty: akce na ticketu i webhook.
Opraveno: rozlozeni dashboardu s vlastnim widgetem se NEDALO ULOZIT.
`validateLayout` znala jen vestaveny katalog, takze kazdy pokus skoncil
hlaskou "widget v katalogu neexistuje" - presne to, co hlasil uzivatel.
Katalog je ted jedna funkce a pouziva ji nabidka i kontrola. Zaroven je
za konkretni firmu, driv slo polozit dlazdici jedne firmy na dashboard druhe.
Prokliky: z widgetu lidi na cloveka, ze seskupeni na vyfiltrovany seznam
ticketu. Odkazy sklada server, protoze on jediny zna filtr widgetu. Seznam
ticketu cte filtr z adresy a umi filtrovat na typ, tag a skupinu.
Tabulky: spolecna `TicketTable` pro seznam i detail osoby. Na mobilu se
neposouva do strany, uzka obrazovka dostane karty. Detail osoby ma velkou
tabulku se zalozkami "ma u sebe" a "vyresil" a prepinacem pohledu.
Odebrano: simulace vcetne tlacitka, dialogu i endpointu. Trojice pohledu
nad tickety - vyber firmy je select, "moje" je prepinac, driv to delalo
totez dvakrat.
Pridan zmereny rozbor kapacity pro 200 firem (19-kapacita-200-firem.md):
soucasny stav to nezvladne, protoze data jsou v pameti a vypis je linearni.
Zmereno na 5 000 ticketech, vcetne toho, co s tim a kolik serveru to chce.
Overeno 7 kontrolami proti bezicimu serveru.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Jakakoliv udalost se muze stat ticketem. Prijem je verejny endpoint na firmu
(`POST /webhook/ticket/:token`), takze zalozit ticket jde i bez stavby stromu.
Externi ID je unikatni V RAMCI FIRMY: dalsi zprava se stejnym ID se navesi na
existujici ticket misto zalozeni druheho, a stejne ID u jine firmy je jiny
ticket. Cislo a retezec jsou tentyz klic. Udalosti se drzi cele vcetne
prijatych dat a jdou rozbalit v detailu - je to neco jineho nez log.
Ticket nove nese firstResponseAt, resolvedAt, resolvedById a reopenCount.
Bez nich neslo rict, kdo kolik odbavil ani jak dlouho zakaznik cekal.
`getAgentStats` z toho pocita vykon resitelu vcetne medianovych casu
a vracenych ticketu. Pocet vyresenych sam o sobe odmenuje toho, kdo tickety
zaviral predcasne, proto je vraceni videt vedle nej.
Widgety: klient konecne vola /widget-data. Endpoint existoval, ale nikdo ho
nepouzival, takze vlastni widget hlasil "nepodarilo se zobrazit". Pribyl zdroj
`connector` - co umi zjistit napojena sluzba, jde vytahnout do dlazdice pres
tentyz skript, ktery pouziva krok automatizace. Vysledek se cachuje.
Akce a widgety uz nejsou v nastaveni, maji vlastni zalozku vedle automatizaci.
Telo akce se sklada stromem, ne JSONem v textarei - je to tentyz editor,
jen misto karty spoustece je "spousti clovek tlacitkem na ticketu".
Nova zalozka Lide se seznamem a detailem osoby. Seznam ticketu i lidi ma dva
pohledy, tabulku a dlazdice.
Opraveno: createTicket bral typeId, fields, tags i assigneeGroupId, ale nikdy
je neukladal. Ticket zalozeny s typem zustaval bez typu a bez vlastnich poli.
Dlouhe pomlcky, sipky, vypustky a bullety pryc z celeho projektu.
Overeno 21 kontrolami proti bezicimu serveru v rezimu souboru.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
.gitignore mel vzorec `data/`, ktery se shodl i se `src/data/`. Sestnact
zdrojovych souboru tim tise chybelo v gitu vcetne cele slozky
`src/data/store/`. Opraveno na `/data/`, stejne v .dockerignore.
Tickety vcetne logu, automatizace, incidenty a rozlozeni dashboardu se po
kazde zmene ukladaji. Pomocnik `withMirror` je opak `withCache`: data se meni
v pameti a zapisuji cela, misto aby se po zapisu znovu nacitala. Citace ID se
pri startu dopocitaji z ulozenych zaznamu, takze novy ticket neprepise stary.
Detail ticketu umi typ, tagy, vlastni pole typu a prehozeni na skupinu.
Nastaveni ma prepnuti spravce na jiny ucet, vychozi jen pro cteni.
Skupiny resitelu chodi spolu s lidmi jednim requestem.
Dokumentace: rejstrik znovupouzitelnych funkci (15), navrh monetizace
a ceny za krok (16), popis nastaveni a prav (17). Doplneny endpointy
do openapi.ts, petice CRUD rout se generuje jednou funkci.
Overeno v rezimu souboru: zmeny prezily tvrde ukonceni procesu a po restartu
byly zpatky vcetne logu ticketu.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Mockup se k databazi nedostane, takze pribyl treti rezim: JSON soubor. Prezije
restart procesu i containeru, ale ne redeploy - filesystem containeru je
docasny. Je to mezistupen, ne nahrada databaze, a tak je to i napsane v portalu.
| Rezim | Kdy | Restart | Redeploy |
| -------- | --------------------------------- | ------- | -------- |
| postgres | DATABASE_URL i SECRETS_KEY | prezije | prezije |
| file | neni DB, ale je DATA_DIR | prezije | ne |
| memory | ani jedno, nebo nejde zapsat | ne | ne |
Rozhodnuti zustava na jednom miste (src/data/connectorStore.ts).
Pridano:
- src/data/snapshot.ts: atomicky zapis (.tmp a prejmenovani), slucovani zapisu
a dokonceni rozepsaneho zapisu pri SIGTERM. Bez atomickeho zapisu by pad
uprostred nechal polovicni JSON, ktery se pri startu nenacte. Rozbity soubor
se prejmenuje na .broken a jede se dal - aplikace, ktera nenastartuje, je pro
AppFactory nefunkcni sluzba
- src/data/connectors/local.ts: jeden kod pro pamet i soubor, lisi se jen tim,
kam se zapisuje. Nahrazuje memory.ts, dve implementace by se casem rozesly
- klic k sifrovani se mimo databazi vygeneruje do DATA_DIR/secrets.key s pravy
0600, takze sifrovani funguje bez nastaveni. Chrani to proti nahodnemu
precteni JSONu, ne proti pristupu k disku - klic lezi vedle dat a je to tak
napsane i v portalu. U databaze se negeneruje vubec: kdo ma zalohu tabulky,
ma i klic ze stejneho stroje
- DATA_DIR v konfiguraci, data/ v .gitignore a .dockerignore
- hlaska v portalu rozlisuje tri nasledky: pamet, soubor a databaze
Overeno bez databaze: konektor s vyplnenymi udaji prezil restart, v JSONu jsou
hodnoty sifrovane a plaintext v nem neni. Pote s databazi: rezim postgres
funguje dal a klic vedle dat se nevygeneroval. Kontejner i data/ po overeni
smazany.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Pristupove udaje konektoru se ukladaji do databaze a prezijou restart. Popis
v documentation/14-databaze.md.
Databaze je volitelna a rezimy jsou oddelene:
- postgres kdyz je DATABASE_URL i SECRETS_KEY
- memory jinak, tedy pri nasazenem mockupu a lokalnim vyvoji bez DB
Rozhodnuti je jen na jednom miste (src/data/connectorStore.ts). Nikde jinde se
nezjistuje, jestli databaze je - kdyby se to rozlezlo po kodu, jedno misto by se
zapomnelo a chovalo by se pak jinak nez zbytek.
Chybejici databaze nesmi shodit start: container, ktery nenastartuje, je pro
AppFactory nefunkcni sluzba. Misto toho se do logu napise proc a portal to ukaze
na strance Konektory. Stejne tak kdyz migrace selzou - psat do rozbiteho
schematu je horsi nez neukladat.
Databaze potrebuje oboji. Bez SECRETS_KEY by se udaje ukladaly v plaintextu
a to je horsi nez ztratit je pri restartu: tabulku vidi kazda zaloha a kazdy
dump pri ladeni.
Pridano:
- pool v src/db/pool.ts vcetne transakci a dbFor(tenantId) jako sev pro budouci
oddelenou databazi jednoho klienta
- migrace ze src/db/migrations/*.sql pod pg_advisory_lock, jinak je pri rolling
deployi pusti vsechny instance naraz. Jeden soubor je jedna transakce
- sifrovani AES-256-GCM s nahodnym IV a verzi klice. Nerozsifrovatelna hodnota
nepada, chova se jako nevyplnena a zaloguje se - jeden rozbity konektor nesmi
shodit seznam ostatnich
- /health/ready s pingem do DB. /health na databazi zamerne nezavisi, kratky
vypadek by jinak vedl k restartovani containeru
- GET /api/dashboard/storage a hlaska v portalu o tom, ze data jsou jen v pameti
- jediny vychozi konektor na firmu a sluzbu hlida castecny unikatni index, ne jen
kod. Dva soubezne zapisy by jinak udelaly dva vychozi
Zmeneno: cteni i zapis konektoru je asynchronni, vcetne validace stromu.
Overeno proti Postgresu 16 v kontejneru: migrace, sifrovani v tabulce, preziti
restartu, rozsifrovani spravnym klicem, degradace pri spatnem klici, PATCH bez
tajneho pole, prepnuti a smazani vychoziho konektoru, pametovy rezim bez
DATABASE_URL. Kontejner po overeni smazan.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Transformace dat ve dvou rezimech plus oprava chyby, kvuli ktere se neukladaly
pristupove udaje konektoru. Popis v documentation/13-transformace-dat.md.
Kroky si predavaji i cele struktury:
- FieldType ma object a list. Do sablony se nedosazuji, predavaji se jako celek
dalsimu kroku - proto je u nich v builderu vyber a ne textove pole. Z podminek
nad nimi ma smysl jen "prisla / neprisla"
- strop na velikost struktury (SCRIPT_MAX_VALUE_BYTES, vychozi 256 kB). Radek
s vystupem kroku je nejrychleji rostouci tabulka v systemu
Dva rezimy transformace, oba nad enginem v src/scripts/mapping.ts:
- transform.map-fields: pole na pole s prevody, klikatelne
- transform.to-json: sablona cileveho objektu s ${cesta}
Marker ${...} je zamerne jiny nez {{...}}. Sablony kroku se dosazuji driv, nez
krok bezi, takze {{total}} by strom stihl vyhodnotit, nenasel by parametr toho
jmena a dosadil by prazdno. Cely retezec navic zachova typ, takze
"unitPrice": "${total}" vyrobi cislo - jinak by cizi sluzba dostala castku jako
text a odmitla ji.
Prevod map pro seznamy je to, bez ceho by priklad nesel dokoncit. Bez nej jde
prevest hlavicku dokladu, ale ne polozky objednavky, a doklad by byl na nulu.
Dal pridano:
- idoklad.create-invoice-from-object: druha polovina prikladu, bere hotove telo
dokladu z transformace a doplni povinna pole ze vzoru iDokladu
- spoustec e-shopu predava celou objednavku jako objekt a polozky jako seznam
- klikaci editor pravidel vcetne rezimu JSON pro vnorena pravidla u map
- kontrola JSONu a tvaru pravidel uz pri ulozeni stromu. Preklep je nedodelek,
ne chyba ukladani - rozdelana prace se nezahazuje
Opraveno: konektor neukladal pristupove udaje. Server byl v poradku, overeno
volanim POST i PATCH. Chyba byla v prohlizeci: u pole type="password" prohlizec
ignoruje autocomplete="off" a dosazuje ulozene prihlaseni. Uzivatel pak videl
jednu hodnotu, React drzel jinou, a ulozilo se to, co drzel React, tedy nic.
Resi to autocomplete="new-password", jmena poli, ktera nepripominaji heslo,
a prepinac zobrazeni, aby slo overit, co je opravdu zapsane.
Zakladani a uprava konektoru se presunuly do dialogu, na strance jsou jen male
karty. Formulare rozlozene po strance byly u vic konektoru neprehledne.
Overeno: npm run typecheck prochazi na serveru i webu, node --check na skriptech.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Slovo "konektor" v kodu znamenalo katalog toho, co umime. Ted znamena napojeni
jedne firmy, tedy to, co tim mysli i uzivatel. Popis modelu je
v documentation/12-sluzby-a-konektory.md.
Tri vrstvy:
- Sluzba: ze iDoklad existuje, co umi a co potrebuje k napojeni. Nase.
- Skript: kod, ktery jednu operaci sluzby opravdu vykona. Nas.
- Konektor: ucet firmy vcetne jejich pristupovych udaju. Firemni.
Pristupove udaje se prestaly cist z environment variables. Cela instance by
mela jedny udaje spolecne a dve firmy by fakturovaly z jednoho uctu. Napojeni
je vlastnost firmy, ne prostredi. Z prostredi zustava jen SERVICES_BASE_URL.
Pridano:
- src/data/services.ts: sluzba nese general, appId, visibility, credentials
a verifyPath. Kategorie "obecne" sdruzuje veci, ktere ma kazdy a nepotrebuji
konektor: webhook, planovac, tickety, transformace dat, HTTP pozadavek,
pauza, zapis do logu
- viditelnost sluzby: vsichni, jen uvedene firmy a lide, nebo jen spravce
platformy. Neviditelna sluzba se z API nevraci vubec, ne se stavem 403 -
firma nema poznat, ze takova sluzba existuje
- src/data/connectorStore.ts: konektory za firmu vcetne hodnot udaju. Hodnoty
se z API nikdy nevraci, jen filled a missing. Prazdne pole hodnotu nemeni,
takze ulozeni formularu bez tajnych hodnot nic nepresepe
- FlowStep.connectorId: krok rika, pod kterym napojenim volat. null = vychozi
konektor firmy, diky tomu je vzorovy strom prenositelny mezi firmami
- overeni konektoru pres verifyPath, tedy cteci volani vyzadujici autorizaci.
U sluzby bez nej se overi jen dostupnost a odpoved to rekne nahlas, jinak by
zeleny vysledek uzivateli lhal
- stranky /dashboard/sluzby a /dashboard/konektory vcetne formularu udaju
- endpointy /api/dashboard/services a CRUD /api/dashboard/connectors ve Swaggeru
- predvyplnene prihlaseni spravcem platformy a prepinac demo uctu na login
strance, kvuli testovani prototypu
Zmeneno:
- stav "napojeno" se prestal cist z katalogu a zacal pocitat z konektoru firmy.
Sluzba ma jen available nebo planned
- validace stromu overuje i konektor. Cizi konektor je chyba, chybejici
napojeni nedodelek - rozdelana prace se nezahazuje
- prejmenovani napric kodem: Connector na Service, FlowStep.connectorId na
serviceId, GET /connectors na GET /services, connectorIcons na serviceIcons,
stranka Konektory (katalog) na Sluzby. Prevodni tabulka je v dokumentu 12
Overeno: npm run typecheck prochazi na serveru i webu.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Konektory dostaly vykonnou cast. Jeden skript je jeden soubor, ktery nese
manifest (vstupni a vystupni parametry) i kod. Diky manifestu s nim umi
pracovat strom automatizace, aniz by o kodu cokoliv vedel.
Soubory jsou zamerne obycejny JavaScript, ne TypeScript. TypeScript by se
musel prelozit a to je presne to otaceni, ktere tady nema byt. Registr
sleduje cas zmeny souboru, takze uprava v portalu, rucni uprava souboru
i novy soubor ve slozce funguji stejne a bez restartu.
Pridano:
- scripts/ se skripty konektoru, nazev souboru je zaroven ID operace
- kontrola vstupu i vystupu proti manifestu, jedna funkce pro obe strany.
Chybejici povinny vystup je chyba skriptu, ne uzivatele - jinak by strom
veril parametru, ktery nikdy nedosel
- ctx predavany skriptu: http nad adresou napojeni, util, log, config,
idempotencyKey, fail a retry. Skript nedostane pristupove udaje
- rozliseni opakovatelne a koncove chyby. Runner nikdy nevyhodi vyjimku,
vzdy vraci vysledek vcetne retryable
- redakce tajnych hodnot pred zapisem do logu. Cizi API rado vraci prijaty
token v chybove zprave a log ticketu vidi klient
- napojeni z environment variables vcetne iDokladu
- sest ukazkovych skriptu pro iDoklad proti skutecnemu API sluzby
services.csbot.cz/apps/idoklad, kazdy na jiny vzor
- stranka /dashboard/skripty: seznam, manifest, editor, zkusebni spusteni.
Formular testu se sklada z manifestu, nepise se pro kazdy skript
- endpointy /api/dashboard/scripts vcetne Swaggeru
Zmeneno:
- katalog konektoru uz neni jen staticky seznam. Akce ze skriptu se domeruji
prekryvem v src/data/connectors.ts, takze se naraz objevi ve validaci
stromu, ve vypoctu scope i v sablonach. Pri stejnem ID vyhrava skript
- ConnectorOperation ma implementation a scriptId
- ApiError na klientovi nese cele telo odpovedi a umi z nej vytahnout issues
- Dockerfile kopiruje scripts/ do vysledneho image
Ukladani nemuze rozbit fungujici skript: kod se nejdriv zapise do docasneho
souboru, ten se nacte a overi, a az pak prepise puvodni.
K tomu tri dokumenty navrhu dalsich kroku: 09 datove modely a prava,
10 runtime a rozpocet na 150 klientu, 11 popis skriptu konektoru.
Overeno: npm run typecheck prochazi na serveru i webu.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Portal nemel zadnou tenanci. Kterykoliv prihlaseny uzivatel videl vsechny
tickety vsech firem i cely seznam resitelu, requireRole se nikde nevolal.
Tenant je hranice viditelnosti, tenantId na ticketu, resiteli i automatizaci.
Uzivatel muze patrit do vic firem, v kazde s jinou roli. Pristup napric firmami
je zvlast jako platformAdmin.
Tri pohledy na tickety: all, tenant, mine. Admin mezi nimi prepina vcetne
vyberu firmy. O pravech rozhoduje jedine data/access.ts, klient si nic
nedovozuje a bere je z GET /api/dashboard/access.
Filtr na firmu je v ulozistich povinny argument, takze zapomenuty filtr
neznamena vse, ale nezkompiluje se. Cizi firma vraci 403 nebo 404, nikdy
tise zuzeny vysledek.
Prirazeni jen v ramci firmy. Prehazovat praci mezi lidmi smi jen admin,
agent si smi vzit ticket na sebe.
Zmena prihlasovani: ucet klient@firma.cz zanikl, demo ucty jsou nove.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ticket dostal telo (body) a odkaz na zdrojovou zpravu. Predmet je shrnuti,
telo je cely text pozadavku.
Akce maji nastavitelna pole (inputs) se sablonami {{parametr}}. Zatim ticket,
kanaly, CRM a AI, ostatni maji jen napovedu.
Krok vidi parametry spoustece plus vystupy kroku pred nim, takze jde vlozit
predvalidaci a vetvit se podle jejiho vysledku. Vetev podminky nepridava nic
do sekvence za podminkou.
Nove konektory Facebook Messenger a Instagram, nova akce RAYNET Dohledat firmu.
Ctyri vzorove automatizace v rozdeleni jedna na kanal pro prijem
a jedna spolecna pro smerovani na resitele.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Web a portal Automia v jednom containeru. Express obsluhuje API
i zbuildovanou React aplikaci z dist/public.
Obsah:
- verejny web: homepage, sluzby, o nas, kontakt, 404
- prihlaseni pres JWT, demo ucty
- portal: prehled s grafem, tickety, incidenty, automatizace, konektory
- builder automatizaci: strom akci, vetveni podminkou
- katalog 25 konektoru v 8 kategoriich
- webhook s registrovanou adresou, token generuje server
- zivy dashboard pres SSE vcetne simulace provozu
- Swagger UI na /docs a OpenAPI na /openapi.json
Soulad s AGENTS.md:
- ROOT_PATH z prostredi, prefix proxy nikde nehardcodovan
- mount na koren i na prefix, funguje s handle_path i bez nej
- base tag a window.__BASE_PATH__ vkladane do index.html za behu
- OpenAPI servers obsahuje prefix, Try it out vola spravnou adresu
- povinne /health a /docs, port 3000, naslouchani na 0.0.0.0
- secrets jen z environment variables, nikdy v logu
Dokumentace ve slozce documentation/.