Commit Graph
6 Commits
Author SHA1 Message Date
JiriUhlirandClaude Fable 5.1 104ae36783 Revize projektu: prava, vykon, runtime, portal a ARES
Prava a bezpecnost: spravce firmy uz nemuze nastavit priznak spravce
platformy ani clenstvi v cizi firme; pozvanky, konektory a automatizace
kontroluji sve pravo; cizi firma v query je 404; zivy stream posila
udalosti jen firmam, kterych se tykaji; akce nad ticketem maji kontrolu
prava za firmu ticketu a strop viditelnosti; tokeny se nelogujou; limit
pokusu na prihlaseni, kontakt a pozvanky; bezpecnostni hlavicky;
zachyceni chyb v async handlerech; timing-safe porovnani tokenu.

Vykon: audit neskenuje celou kolekci pri kazdem zapisu a konecne maze
firemni zaznamy; ticket se uklada jednou misto trikrat; zapisy do
Postgresu jsou serializovane podle ID; prava se pocitaji jednou na
request; widgety nacitaji tickety jednou; strankovani seznamu; worker
je pool misto kol; na webu udalost ze streamu neodmontuje stranku,
dotazy maji spolecny debounce a cache, ciselniky drzi typovany sklad.

Runtime: opakuji se jen chyby oznacene retryable; smycka nenarazi na
strop 50 kroku (novy strop 1000 akci); podminka nad datem funguje;
vystup MCP nastroje neprepisuje spoustec; sandbox skriptu firmy nejde
opustit; MCP session id se drzi mezi volanimi; incident z kroku patri
firme; jedno rozhodnuti o rezimu uloziste; snapshot neprepise soubor
po chybe cteni.

Refaktory: sdilene typy API v src/shared (web nic nekopiruje, osm
rozjetych tvaru sjednoceno); spolecny modul net/guard pro volani ven;
formularova vrstva ui/form; rozdeleni Connectors a FlowCanvas; jeden
helper pro firmu z query, validaci a CRUD udalosti; pomucky ctx.util
pro skripty konektoru; i18n verejneho webu vcetne anglictiny.

Nova funkce: zalozeni firmy z registru ARES v Nastaveni (IC nebo nazev,
dotazeni IC, DIC, sidla a pravni formy, vyber soucasnych statutarnich
zastupcu a prokury, ucty spravce firmy s nahradnim e-mailem
IC-poradi@placeholder.cz).

Dokumentace: zaznam v 99-zmeny.md a aktualizace 15 dalsich dokumentu.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 10:26:07 +02:00
JiriUhlirandClaude Opus 5 9f4b8c0596 Podminka se muze ptat na vic veci naraz
Podminka byla prave jedna otazka. Slozitejsi vetveni se muselo skladat
z vnorenych podminek, takze "vysledek dorazil" a v nem "vysledek je X" byly dve
urovne stromu misto jedne vety. U tri hodnot, ktere maji dopadnout stejne, to
byly tri urovne.

Model: rules (otazky) a match ('all' = a zaroven, 'any' = nebo). Stara podoba
fieldId/operator/value primo na kroku se dal cte, prevadi ji rulesOf - jedine
misto, kde se to deje. Kdyby se fieldId cetlo primo, krok ulozeny driv by po
zmene modelu prisel o svou otazku a vetvil by vzdycky stejne, tise a bez chyby.
Zapisuje se uz vzdycky rules.

fieldId je v typu nepovinne schvalne: prekladac tim ukazal vsech pet mist,
ktera podminku ctou (vyhodnoceni, obe hlasky do logu, kontrola pri ukladani,
kontrola nedodelku).

Proc jedna uroven a ne vyrazy se zavorkami: dve treti podminek jsou "vsechny
tohle" nebo "cokoliv z tohohle". Zavorky by v rozhrani znamenaly editor vyrazu,
ktery uz nikdo neuklika, a textovy zapis by navic zahodil odkaz na parametr pres
ID - diky nemu prejmenovani parametru podminku nerozbije a builder umi nabidnout
jen operatory, ktere na dany typ sedi. Az se ukaze, ze jedna uroven nestaci,
da se textovy zapis pridat nad tentyz vyhodnocovac. Opacne to nejde.

V builderu radek na otazku, tlacitko "Přidat otázku" a od druhe otazky prepinac
"sedí všechny" / "sedí aspoň jedna". Posledni otazka nejde smazat: podminka bez
otazky by tise nevetvila, runtime ji povazuje za nesplnenou a zaloguje to.

Radek podminky v logu nese vsechny otazky i s tim, ktera rozhodla - bez rozpadu
by u spojene podminky bylo videt jen "nesplneno".

Overeno na bezici instanci, strom se dvema spojenymi podminkami a peti volanimi:
vysledek nedorazil (nic), Chybějící informace (prirazeno), Přesměrování
a Mimo téma (zavreno pres any), Něco jiného (nic). Strom ulozeny ve stare
podobe se nacte a ulozi beze zmeny.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 09:16:19 +02:00
JiriUhlirandClaude Opus 5 1532ea0af7 Automatizace zavirala tickety uz pri zvoneni
Na instanci nebyl ani jeden nevyrizeny ticket: vsech 131 melo closed true,
takze dlazdice "Moje tickety" i "Fronta bez resitele" ukazovaly nulu. Widget
pocital spravne, spatna byla data.

Strom se ptal `result neq "Chybějící informace"` a ve vetvi ANO ticket zaviral.
Jenze jeden hovor posle vic zprav a ta prvni jen ohlasi, ze zacal: data je null,
takze {{result}} je prazdne. evaluate prevadi chybejici hodnotu na prazdny
retezec, a prazdno se opravdu nerovna "Chybějící informace" - podminka tedy
sedla a ticket se zavrel uz pri zvoneni.

Odtud i druha vec: prirazovaly se i tickety, ktere nemely. Jedna zprava hovoru
mela result "Chybějící informace", takze ticket sel na servicedesk a na cloveka.
Dalsi zprava tehoz hovoru prinesla "Přesměrování" a ticket zavrela, ale resitele
uz neodebrala. Z dvaceti ticketu prirazenych jednomu cloveku jich melo
"Chybějící informace" jen deset.

Opraveny strom se nejdriv pta, jestli vysledek vubec prisel, a teprve pak
kladne, jestli je to "Chybějící informace":

  result isNotEmpty
    ANO  result eq "Chybějící informace"
           ANO  priorita vysoka, sekce Servicedesk, prirazeni nejvolnejsimu
           NE   zavrit
    NE   nic, vysledek jeste nedorazil

Puvodni neq znamenalo "vsechno ostatni vcetne toho, co jeste nevime". To je
u nepovinneho parametru, ktery dorazi az pozdejsi zpravou, past.

Radek podminky v logu rikal jen "splneno". Ted nese i to, s cim se porovnavalo,
a rozlisuje nedorazilo od prazdneho - prvni radek je presne ta informace, ktera
chybela:

  Podmínka: result isNotEmpty: nesplněno   | result = nedorazilo
  Podmínka: result eq Chybějící informace  | result = "Přesměrování", porovnáno s "Chybějící informace"

Overeno prehranim celeho hovoru na bezici instanci: prvni zprava bez dat ticket
zalozi a nechá otevreny, druha s "Chybějící informace" ho preda a priradi, treti
s "Přesměrování" ho zavre. Pred opravou byl vyrizeny uz po prvni zprave.

Zustava k rozhodnuti: pri prehodnoceni hovoru resitel na zavrenem ticketu
zustane. Do "Mych ticketu" nespadne, ty ukazuji jen nevyrizene, ale ve vsech
ticketech u nej to jmeno stoji.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 09:01:09 +02:00
JiriUhlirandClaude Opus 5 1134852bff Zalozit nebo doplnit ticket: doplneni konecne doplnuje
Krok mel v poli Obsah nastaveno {{rating}}. Data v behu prokazatelne byla,
v udalostech ticketu je hodnoceni videt cele, ale ticket zustal s prazdnym
obsahem.

intakeEvent deli praci na zalozeni a navazani na existujici ticket a vsechno
z `create` platilo jen pro tu prvni vetev. U existujiciho ticketu se doplnovaly
pouze vlastni pole a stitky, zbytek se tise zahodil. U hovoru to znamena, ze
obsah nedorazi nikdy: prvni zprava jen oznami, ze hovor zacal (in-progress,
data null), a prave ta ticket zaklada. Hodnoceni prijde az posledni zpravou,
kdy uz ticket existuje. Stav byl jedina vyjimka, protoze ho krok nastavuje
zvlast pres updateTicketStatus - proto fungoval a zbytek ne.

Jedno pravidlo misto dvou seznamu poli:
- neprazdna hodnota prepise, prazdna nemaze. IntakeInput ma na to `apply`,
  v `create` zustala jen zaloha predmetu a vychozi stav
- prazdna hodnota nemaze schvalne. Prave to byla puvodni obava, kvuli ktere se
  zapisovalo jen pri zalozeni: pozdejsi zprava bez jmena zakaznika je bezna
  a smazat kvuli ni jmeno by bylo horsi nez ho nedoplnit
- vyjimky zustavaji dve: zaloha predmetu z externiho ID plati jen pri vzniku
  a stav chodi pres updateTicketStatus, ktere resi i priznak vyrizeni, cas
  vyreseni a pocet znovuotevreni

Data smi chodit po castech:
- vlastni pole typu se scitaji podle klicu. Prvni zprava posle `data`, druha
  `data2` a ticket ma obe
- prazdny retezec pole nemaze. Sablona, ktera na nic neukazuje, se dosadi
  prazdnem, takze {"vysledek":"{{result}}"} u zpravy bez vysledku posilalo
  prazdno a prepsalo tim hodnotu z minule zpravy. Vymazat pole jde poslanim
  null, coz uz je zamer

Dalsi dve veci, ktere u toho vyplavaly:
- create.status se do createTicket vubec nepredaval, takze ticket vznikl
  s vychozim "Nový" a hned se prepsal. V logu pak stalo "stav Nový ->
  completed" u ticketu, ktery v nem nikdy nebyl
- faze byla zrusena uz driv, ale v katalogu po ni zbyval krok "Posunout do
  dalsi faze" a pole Faze u zalozeni ticketu. Ticket ani typ ticketu fazi
  nemaji, takze krok by selhal na chybejicim skriptu a pole se zahazovalo.
  Oboji je pryc

Krok navic v logu rekne, co doplnil: "doplnen TK-123, stav completed, obsah".
Driv radek jen oznamil, ze se ticket doplnil, a nebylo poznat cim.

Overeno na bezici instanci s vlastnim DATA_DIR, tremi zpravami o jednom hovoru:
prvni zaklada ticket s prazdnym obsahem, druha doplni obsah i zakaznika, treti
bez dat je nechava byt a meni jen stav. Scenar s `data` a pak `data2` ma na konci
obe hodnoty.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 08:09:05 +02:00
JiriUhlirandClaude Opus 5 a771834e57 Realne sluzby, OpenAI, odesilani e-mailu a helpdesk
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>
2026-08-26 07:40:16 +02:00
JiriUhlirandClaude Opus 5 a57eca123e Fronta a worker: webhook odpovi hned, praci udelaji workeri
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>
2026-08-13 16:41:02 +02:00