Pulse (PulseLab)
S60 Pulse — Marketing AI Specialist, konverzační agent platforma.
| Owner | pulse (agent) |
| Status | running |
| Repo | studio60-cz/pulse |
| Stack | NestJS + TypeScript + Redis + PostgreSQL |
| Hub URL | https://pulse.s60hub.cz |
| Prod URL | https://pulselab.cz |
| Catalog | catalog/services/s60pulse.yaml |
DB
| Env | DB | User | Server | Port |
|---|---|---|---|---|
| Dev | s60_pulse_dev |
s60_pulse_dev_user |
cerebro (pg-alfa) | 6432 |
| Hub | s60_pulse_hub |
s60_pulse_hub_user |
hub-alfa (pg-alfa) | 6432 |
| Prod | s60_pulse_prod |
s60_pulse_prod_user |
prod-alfa (pg-alfa) | 6432 |
Architektura
| Container | Port | Role |
|---|---|---|
s60-pulse |
3100 | NestJS API + Vite admin + Vite client + Chromium (PDF) |
Jedna aplikace servíruje:
/admin— Admin panel (Vite SPA)/app— Klientská aplikace (Vite SPA)/api/*— REST API (NestJS)
Závislosti
- redis (cache, queues)
- postgres-managed (pg-alfa, port 6432)
- billit (checkout / kredity — viz billit/INDEX.md § Integrace)
- s60auth (OAuth2 login)
- Anthropic API (Claude pro AI analýzy — klíč v
.env) - Chromium (Puppeteer pro PDF generování)
~~Známé Dockerfile bugy~~ — ✅ VYŘEŠENO (ověřeno 2026-08-02)
Všechny tři bugy jsou opravené přímo v repu, lokální patche na hub/prod už nejsou potřeba:
- ~~
npm cibez--legacy-peer-deps~~ → v Dockerfile je na všech 4 výskytech - ~~
COPY --from=builder /app/admin/dist/~~ → v repu není (správněadmin-dist) - ~~
COPY --from=builder /app/client/dist/~~ → v repu není (správněclient-dist)
Object Storage — bucket s60pulse (CR-222)
| Endpoint / region | https://fsn1.your-objectstorage.com / fsn1 (Falkenstein) |
| Bucket | s60pulse |
| Prefix | REVIEW_MEDIA_S3_PREFIX = hub / prod — BEZ koncového lomítka! |
| Přístupy | /root/secrets/objectstorage/fsn1-media.env (propsáno do .env.<env> pulsu) |
| CORS | nastavil pulse (PUT z pulselab.cz, www., hub., pulse.s60dev.cz) |
⚠️ Koncové lomítko v prefixu = produkční incident. Kód si lomítko dává sám
(review-media-store.ts: `${this.prefix}/${tenantId}/…`). Hodnota prod/ vyrobila klíče
prod//…, které po pozdější opravě prefixu přestaly projít kontrolou vlastnictví → klientovo
video nešlo přehrát ani schválit (2026-08-03). Od 5141fc0 se prefix normalizuje při načtení,
ale hodnotu stejně pište bez lomítka. Viz [[feedback_env_value_shape]].
⚠️ Klíč je PROJEKTOVÝ, ne bucketový — vidí i na s60backup. Vědomě přijaté riziko,
oddělení Hetzner projektů plánuje Libor. Viz [[project_s3_key_scope_risk]].
Lifecycle pravidla (nastaveno 2026-08-03 sentinelem)
| pravidlo | prefix | akce |
|---|---|---|
expire-pending-dev |
dev/pending/ |
smazat po 1 dni |
expire-pending-hub |
hub/pending/ |
smazat po 1 dni |
expire-pending-prod |
prod/pending/ |
smazat po 1 dni |
abort-incomplete-multipart |
(celý bucket) | zrušit nedokončený multipart po 7 dnech |
Pending objekty vznikají při přímém nahrání z prohlížeče (<prefix>/pending/<tenantId>/<uuid>);
když klient zavře okno mezi zápisem a potvrzením, zůstanou tam navždy. Multipart pravidlo řeší
tutéž třídu problému u velkých souborů — rozpracované části se neukazují v běžném výpisu, ale
účtují se.
# ověření
aws --endpoint-url https://fsn1.your-objectstorage.com \
s3api get-bucket-lifecycle-configuration --bucket s60pulse
⚠️ Pravidlo matchuje přesný prefix, takže případný legacy klíč prod//pending/… (dvojité
lomítko) by pod něj NESPADL. Dnes žádný takový neexistuje a 5141fc0 jejich vznik znemožňuje.
Úložiště video recenzí (REQ-049)
| Bind | /mnt/pulse-reviews → /data/reviews (od 9012e6c, 2026-08-02) |
| Env | REVIEW_MEDIA_DIR nenastaveno — platí default /data/reviews |
| Backing | ⚠️ lokální disk hub-alfa i prod-alfa — přechodný stav |
| Cílový stav | S3 / Hetzner Object Storage (hel1) — rozhodl Libor 2026-08-02; TODO pulsu T-20260802-088, čeká na jejich spec |
Proč NE Storage Box mount (rozhodnuto 2026-08-02)
Storage Box existuje (u560655.your-storagebox.de, sub-účet u560655-sub2, domov pulse-reviews/,
zřízený infrou 17. 7. pod taskem T-20260717-012 — který je ale FAILED a složka je dodnes prázdná).
Připojit ho přes SSHFS by ale reprodukovalo přesně tu vadu, kterou REQ-049 opravuje:
Když FUSE mount spadne za běhu, bind v kontejneru nehlásí chybu — ukáže prázdný zapisovatelný adresář. Upload projde, klient dostane potvrzení, soubor je pryč.
K tomu FUSE přes síť pro soubory do 500 MB a pořadí mount vs. start kontejneru při bootu. S3 tuhle třídu chyb nemá: zápis buď vrátí 200, nebo výjimku — není stav „vypadá to, že to šlo".
⚠️ Nepřipojovat Storage Box na hub/prod ani jako „dočasné řešení". Pokud by bylo potřeba
off-site kopii dřív než S3, správná varianta je rsync timer (vzorem je s60-storagebox-sync.sh,
kterým jezdí zálohy) — aplikace pak na síti nezávisí.
⚠️ Bez tohoto bindu upload NESELŽE — soubor se uloží do zapisovatelné vrstvy kontejneru,
klient dostane potvrzení a obsah zmizí s dalším recreate. Vada byla tichá, ne hlučná.
Jak ověřit, že bind funguje (nestačí docker compose config ani zelený deploy):
docker container inspect s60-pulse --format '{{range .Mounts}}{{.Type}} {{.Source}} → {{.Destination}}{{println}}{{end}}'
docker exec s60-pulse sh -c 'echo test > /data/reviews/x.txt' # zápis zevnitř
ls -l /mnt/pulse-reviews/ # musí být vidět na hostiteli
# pak recreate a číst znovu — container ID se MUSÍ změnit, jinak to nic nedokazuje
BillIt integrace (prod)
- Env:
BILLIT_TENANT_SLUG=studio-60(Pulse NEMÁ vlastní tenant — orthogonalfeedback_billit_tenant_slug.md) - API key prefix:
blt_6859 - Scopes:
write:orders,read:orders,read:invoices - Webhook:
https://pulselab.cz/webhooks/billit(events:order.paid,order.cancelled) - Webhook secret:
whsec_fea33ba…(uloženo/root/secrets/pulse/billit-integration-prod.env)
Poslední deploy
- 2026-05-23 03:24 UTC — commit
0afd88ahub+prod (T-20260523-002, kód9af0100; Filip projekt switch fix — per-projekt scope pro completed badge + prereq locks (2 iterace:1411848separate UI vs prereq scope →9af0100Liborovo follow-up "prereq taky per-projekt", revert na čistě per-projekt přes jediný completedSlugs); code-only deploy, žádné nové migrace; jest 1118/1118 PASS; /health 200 hub+prod, sequenceNum coverage zůstává 100% (hub 6874/6874, prod 6906/6906), 0 ERROR post-deploy hub i prod; Sentry fix B drží — bundle 1.42 MB, 0× "Invalid Sentry Dsn") - 2026-05-22 15:55 UTC — commit
a52a6c1prod (skok0c121a5 → a52a6c1; migrace 119 + 120 applied id 789/790 v jednom deployi — sequenceNum NULL count 331 → 0 (coverage 100.00%), per-session integrity OK (max_seq = msgs count, žádné gaps); REQ-039 content_blockslanding.{header,midcta}_cta_url="/auth/login?prompt=create"; Sentry fix B verified — bundle 1.42 MB má 0× "Invalid Sentry Dsn"; saveMessage code fix v containeru → další INSERT-y dostávají sequenceNum; pre-deploy backupprod-pre-a52a6c1-20260522-155241.dump9.3 MB; /health 200, 0 ERROR post-deploy; auth+pulse prod stack nyní fully aligned se hub) - 2026-05-22 04:28 UTC — commit
a52a6c1hub (T-20260522-007, kódd966436; sequenceNum gap fix navazující na dress rehearsal finding ze 03:35 — saveMessage code fix (COALESCE(MAX(sequenceNum), 0) + 1při INSERT) + migrace 120 BackfillNullSequenceNum applied id 790; empirický důkaz na prod data: NULL count 299 → 0, coverage 100.00%, per-session integrity OK (min_seq=1, max_seq=msgs count, žádné gaps); /health 200, 0 ERROR; hub continues to mirror prod state + applied 119+120 — dress rehearsal pro prod) - 2026-05-22 03:35 UTC — commit
a5d1a0ehub (T-20260521-017 + T-20260521-025 incident recovery; pulse 537e1b0 migrace 119 selhala — referencovala neexistujícícontent_blocks.statussloupec → hub 502 ~30 min; recovery sekvence: fresh prod dumpprod-source-20260522-032734.dump9.2 MB → safety hub backuphub-pre-prod-clone-20260522-032800.dump8.4 MB → stop container → DROP+CREATE s60_pulse_hub → pg_restore prod dump → deploy.sh auto-pulled pulse fixe6e375f"drop content_blocks.status filter z migrace 119" → migration:run applied 119 OK (count 115→116); Sentry fix B (8ffc89c) verified — bundle 1.42 MB má 0× "Invalid Sentry Dsn" (Vite tree-shake stripped @sentry/browser kompletně kvůli empty VITE_SENTRY_DSN); content_blocks.{header_cta_url,midcta_cta_url}="/auth/login?prompt=create" ověřeno; QA 13/16 PASS — 3 FAIL htmlPath orphaned outputs (pricing-authority, customer-path) bez ohledu na deploy; hub o 1 commit nad prod, backup platí) - 2026-05-20 07:21 UTC — commit
caa4467hub (T-20260520-017, kódd4946e1; @sentry/browser pin7.110.0exact via client/package.json; QA 15/15 PASS; commit verified; SDK skutečně downgraded v containeru — ALE Sentry DSN regex(\w+)se mezi 5.x-10.x NEZMĚNIL → UUID DSN stále padá → fix byl ineffective; pulse fix path A byl postavený na chybné diagnóze sentinelu — actual root cause je Sentry SDK design choice across all versions; viz glitchtip KB pro aktuální workarounds) - 2026-05-20 04:45 UTC — commit
0c121a5prod (skok30c423b → 0c121a5; +migrace 116/117/118 applied id 786/787/788 — sequenceNum + RecoverCorrupted 86 sessions + ai_call_log; sequenceNum coverage 100% prod 6575/6575 messages; +ORDER BY refactor; token už byl v.env.prodz předchozí akce, length 64 v containeru; 0 ERRORů kromě 2 one-shot[GLITCHTIP_NEVER_EVENTS]při bootu (UUID DSN issue z 30c423b, separate fix); /health 200, chat URL 200; pre-deploy backupprod-pre-8bbd33b-20260520-033748.dumpzůstal jako poslední pre-migrace backup) - 2026-05-20 04:16 UTC — commit
0c121a5hub (T-20260520-007, kód42fea6f; ORDER BY refactor dokončující CR-155 Tier 2 — 5×orderBy('m."sequenceNum"', 'ASC', 'NULLS LAST')v dist/conversation/conversation.service.js + SessionService + entity column + integrity banner check; code-only;GLITCHTIP_API_TOKENpřidán do/root/secrets/pulse/.env.{hub,prod}org-level token; GLITCHTIP_NO_TOKEN loop zmizel; nový one-shot[GLITCHTIP_NEVER_EVENTS]při bootu detekuje že Sentry SDK nikdy neposlal event do GlitchTip (UUID DSN issue z 30c423b, separate fix); QA 13/13 PASS) - 2026-05-20 03:39 UTC — commit
8bbd33bhub (T-20260520-002, kóddec2f77; 3 migrace applied 116/117/118 — sequenceNum column + 100% backfill, RecoverCorrupted 86 sessions 444 KB SQL, ai_call_log audit CREATE; pre-deploy backupprod-pre-8bbd33b-20260520-033748.dump8.7 MB) - 2026-05-19 10:57 UTC — commit
30c423bhub+prod URGENT (T-20260519-023, agile deploy; 2 client-side critical fixy:b3d905eChat.tsx missing imports/state pro CR-155 integrity banner — způsobovalo bílou obrazovku pro VŠECHNY klienty kdo otevřou chat;805ba2bSentry init wrap do try/catch —@sentry/react 7.120.4odmítá UUID DSN z GlitchTip → init throw blokoval bootstrap; code-only deploy, žádné migrace; QA 13/13 PASS, /health 200 hub+prod, chat URL 200; sentry DSN errors do GlitchTip stále nefunkční dokud pulse neudělá downgrade nebo přechod na @sentry/browser) - 2026-05-19 07:33 UTC — commit
2ea2d11prod (skok5eedc6e → 2ea2d11, +migrace 115 LandingImagePathsAppPrefix; 0 errů post-deploy; LP HTML 0× bare /landing/ + 6× /app/landing/; HTTP verified/app/landing/clock-lenka.png200,/landing/clock-lenka.png404; 30 min obrázek + ikony nyní viditelné na pulselab.cz) - 2026-05-19 07:30 UTC — commit
2ea2d11hub (T-20260519-019, kód72bac90; fix LP image paths v 4 content_blocks keys; migrace 115 applied; QA 13/13 PASS, HTTP verified) - 2026-05-19 07:15 UTC — commit
5eedc6eprod (skokd3b3387 → 5eedc6e, 19 commitů, 5 nových migrací 110/111/112/113/114; hero_levels seed Spark/Bolt/Storm/Pulsar OK; "Released 113 stale holds" + Startup recovery 1 orphaned; 0 TenantScopedSubscriber errů; LPpulse-logo.png+ jen IG+FB social; ⚠️ video_testimonials live = staré Veronika/Tomáš/Klára (migrace 112 udělala draft, na prod nikdo nepublikoval → Libor musí publish přes admin); migrace 114 no-op na prod (preconditionauthor=Zuzkaneprošla, content má Tomáš H. na idx 1)) - 2026-05-19 07:07 UTC — commit
5eedc6ehub (T-20260519-016, kódb6ea47e; bugfix83408fdLP renderer čtecontent_blocks(overridepage_templates.data);b6ea47elogo PNG všude + IG+FB only + Zuzka "Právnička"; migrace 113+114 applied; QA 13/13 PASS, LP HTML verified) - 2026-05-19 06:34 UTC — commit
a171e7ahub (T-20260519-014, fix 4edf585 — CI mock pro CR-153 watchdog test; deploy 5/8 PASS, npm test 1072/1072 PASS; regrese opravena: 0 TenantScopedSubscriber refusals od bootu) - 2026-05-19 05:42 UTC — commit
73b6fbbhub BLOCKED (T-20260519-010, fix 9b1f5ad — wrap 3 background tasks; deploy zablokován CI [2/8] — 5/1072 test fail v output-queue.service.spec.ts: missing TenantContextService mock; pulse self-reflect "fix nemělo jít out bez npm test"; viz fix 4edf585 níže) - 2026-05-19 05:15 UTC — commit
2389de7hub (T-20260519-004, bundle4c19bb7..2389de7, 12 commits; migrace 110 HeroLevelToneColors + 111 RelinkLegacySessions + 112 LandingVideoTestimonialsDraft applied; hero_levels seed Spark/Bolt/Storm/Pulsar OK; ⚠️ regrese CR-153 watchdog + Output startup recovery + ConversationService cleanupStaleHolds běžely beztenantContext.runWithTenant()→ fixed va171e7a) - 2026-05-16 08:34 UTC — commit
d3b3387hub+prod (CR-148 docs: MCP overview help stránka 327 řádků pro Mísu+Radku, 13 kategorií ~50 MCP tools; oprava landing-editor help; bez migrací) - 2026-05-16 08:17 UTC — commit
e4419b4hub+prod (DEPLOY.md sync po help LP storage fix4c19bb7; bez migrací) - 2026-05-16 07:05 UTC — commit
fbe4897hub+prod (3 URL routing hotfixy:0bd1b2cnavigate()+Link to= bug fix v bd4af36,5767f05logo→LP,f84cd28@ai-guide headers cleanup; bez migrací) - 2026-05-16 06:17 UTC — commit
e378fedhub+prod (CR-148 URGENT URL uklid:3c54bff/app routing loop + footer fakturační údaje; migrace 109 FooterCompanyInfo) - 2026-05-16 05:55 UTC — commit
7eadc05hub+prod (CR-148 LP fixes: descriptions "Jak to funguje" + outputs gap; migrace 108 LandingStepsDescriptionField) - 2026-05-16 04:53 UTC — commit
9b4dd17hub+prod (CR-148 Landing page redesign mockup v2 — plně MCP-editable + Baťa pricing 1/200, 6/900, 20/1900; migrace 106 LandingMcpEditableBlocks + 107 BataPriceUpdate; hub 70 landing blocks seed; 0 výpadek prod, hub 502 ~30 min při fixu RLS bugu v migraci 106 — viz post-mortem v runbooks) - Pre-deploy hub:
148e41b→6cfe392(RLS fix mid-deploy) →8f35d57→9b4dd17→7eadc05→e378fed→fbe4897→e4419b4→d3b3387 - Pre-deploy prod:
8a7b7e5(přímý skok přes 9b4dd17, hub validoval celý bundle) - 2026-05-15 12:41:
8a7b7e5hub+prod (CR-145 pool cascade + AiCostLogger RLS fix + WELCOME/PRICING SQL + backfill v2; QA RELEASE 8/8 PASS prod, 15/15 PASS hub; 0 výpadek; runbookdocs/runbooks/pulse-hub-dress-rehearsal.md) - 2026-04-27:
686dd6c(CR-121 UX guards, T-20260427-188)
Health checks
- Hub:
curl https://pulse.s60hub.cz/health - Prod:
curl https://pulselab.cz/health
Secrets
/root/secrets/pulse/.env.hub/root/secrets/pulse/.env.prod/root/secrets/pulse/billit-integration-prod.env
Známá pravidla
- TypeORM
synchronize: falsevždy (orthogonal:feedback_no_synchronize.md) - Build deps:
slugmigrace + diacritics fix (CR-121 amend) — pozor na typeorm migration:run při deploy - Sentry:
VITE_SENTRY_DSNbuild arg
Změny / historie
- 2026-05-20: deploy 8bbd33b hub (CR-155 Tier 2 sequenceNum + recover 86 sessions + CR-159 ai_call_log; sequenceNum coverage 100%; ⚠️ GLITCHTIP_API_TOKEN env chybí)
- 2026-05-19: deploy 30c423b hub+prod URGENT (agile, code-only; Chat.tsx integrity banner imports + Sentry init try/catch; opravuje bílou obrazovku chatu)
- 2026-05-19: deploy 2ea2d11 prod (skok z 5eedc6e + migrace 115; 0 errů; LP image paths OK na pulselab.cz)
- 2026-05-19: deploy 2ea2d11 hub (image paths fix /landing/ → /app/landing/ 4 keys; migrace 115; HTTP 200/404 verified)
- 2026-05-19: deploy 5eedc6e prod (skok z d3b3387, 19 commitů + migrace 110-114; 0 errů; ⚠️ video_testimonials draft na prod neproshlý publish — staré Veronika/Tomáš/Klára na LP; logo PNG + IG+FB OK)
- 2026-05-19: deploy 5eedc6e hub (LP renderer fix 83408fd content_blocks override + b6ea47e logo PNG/social IG+FB only/Zuzka Právnička; migrace 113+114; QA 13/13 PASS, LP HTML verified)
- 2026-05-19: deploy a171e7a hub (CI fix 4edf585 — test mock pro CR-153 watchdog; regrese opravena, 0 errů, čeká na prod OK)
- 2026-05-19: deploy 73b6fbb hub BLOCKED (fix 9b1f5ad bez updated test mock, CI fail 5/1072)
- 2026-05-19: deploy 2389de7 hub-only (12-commit batch CR-152/153/154/155 + migrace 110/111/112; regrese CR-153 watchdog + Output startup recovery — TenantScopedSubscriber missing runWithTenant wrap; prod blocked)
- 2026-05-16: deploy d3b3387 hub+prod (CR-148 docs: MCP overview help — last in 6-deploy iteration today)
- 2026-05-16: deploy e4419b4 hub+prod (help LP storage fix)
- 2026-05-16: deploy fbe4897 hub+prod (URL routing hotfixy /app/X → /X)
- 2026-05-16: deploy e378fed hub+prod (CR-148 /app routing loop + footer; migrace 109)
- 2026-05-16: deploy 7eadc05 hub+prod (CR-148 LP fixes; migrace 108)
- 2026-05-16: deploy 9b4dd17 hub+prod (CR-148 LP redesign + Baťa pricing; migrace 106+107 s SET LOCAL tenant GUC pattern)
- 2026-05-15: deploy 8a7b7e5 hub+prod (CR-145 cascade + AiCostLogger RLS)
- 2026-04-27: deploy 686dd6c (CR-121 UX guards)
- 2026-04-26: deploy bcbca34 (CR-120 + CR-121)
- 2026-03-30: BillIt prod integrace
- 2026-03-22: CR-118 slug migrace (synchronize disabled)
- 2026-03-15: pulse Dockerfile bug fix (port 3100, ne 3200) — orthogonal
feedback_deploy_dockerfile.md
Plný changelog: docs/deploy/changelog.md (filtrovat pulse).