Skip to content

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:

  1. ~~npm ci bez --legacy-peer-deps~~ → v Dockerfile je na všech 4 výskytech
  2. ~~COPY --from=builder /app/admin/dist/~~ → v repu není (správně admin-dist)
  3. ~~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 / prodBEZ 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 — orthogonal feedback_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 0afd88a hub+prod (T-20260523-002, kód 9af0100; Filip projekt switch fix — per-projekt scope pro completed badge + prereq locks (2 iterace: 1411848 separate UI vs prereq scope → 9af0100 Liborovo 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 a52a6c1 prod (skok 0c121a5 → 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_blocks landing.{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 backup prod-pre-a52a6c1-20260522-155241.dump 9.3 MB; /health 200, 0 ERROR post-deploy; auth+pulse prod stack nyní fully aligned se hub)
  • 2026-05-22 04:28 UTC — commit a52a6c1 hub (T-20260522-007, kód d966436; sequenceNum gap fix navazující na dress rehearsal finding ze 03:35 — saveMessage code fix (COALESCE(MAX(sequenceNum), 0) + 1 př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 a5d1a0e hub (T-20260521-017 + T-20260521-025 incident recovery; pulse 537e1b0 migrace 119 selhala — referencovala neexistující content_blocks.status sloupec → hub 502 ~30 min; recovery sekvence: fresh prod dump prod-source-20260522-032734.dump 9.2 MB → safety hub backup hub-pre-prod-clone-20260522-032800.dump 8.4 MB → stop container → DROP+CREATE s60_pulse_hub → pg_restore prod dump → deploy.sh auto-pulled pulse fix e6e375f "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 caa4467 hub (T-20260520-017, kód d4946e1; @sentry/browser pin 7.110.0 exact 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 0c121a5 prod (skok 30c423b → 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.prod z 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 backup prod-pre-8bbd33b-20260520-033748.dump zůstal jako poslední pre-migrace backup)
  • 2026-05-20 04:16 UTC — commit 0c121a5 hub (T-20260520-007, kód 42fea6f; 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_TOKEN př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 8bbd33b hub (T-20260520-002, kód dec2f77; 3 migrace applied 116/117/118 — sequenceNum column + 100% backfill, RecoverCorrupted 86 sessions 444 KB SQL, ai_call_log audit CREATE; pre-deploy backup prod-pre-8bbd33b-20260520-033748.dump 8.7 MB)
  • 2026-05-19 10:57 UTC — commit 30c423b hub+prod URGENT (T-20260519-023, agile deploy; 2 client-side critical fixy: b3d905e Chat.tsx missing imports/state pro CR-155 integrity banner — způsobovalo bílou obrazovku pro VŠECHNY klienty kdo otevřou chat; 805ba2b Sentry init wrap do try/catch — @sentry/react 7.120.4 odmí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 2ea2d11 prod (skok 5eedc6e → 2ea2d11, +migrace 115 LandingImagePathsAppPrefix; 0 errů post-deploy; LP HTML 0× bare /landing/ + 6× /app/landing/; HTTP verified /app/landing/clock-lenka.png 200, /landing/clock-lenka.png 404; 30 min obrázek + ikony nyní viditelné na pulselab.cz)
  • 2026-05-19 07:30 UTC — commit 2ea2d11 hub (T-20260519-019, kód 72bac90; 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 5eedc6e prod (skok d3b3387 → 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ů; LP pulse-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 (precondition author=Zuzka neprošla, content má Tomáš H. na idx 1))
  • 2026-05-19 07:07 UTC — commit 5eedc6e hub (T-20260519-016, kód b6ea47e; bugfix 83408fd LP renderer čte content_blocks (override page_templates.data); b6ea47e logo 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 a171e7a hub (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 73b6fbb hub 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 2389de7 hub (T-20260519-004, bundle 4c19bb7..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 bez tenantContext.runWithTenant() → fixed v a171e7a)
  • 2026-05-16 08:34 UTC — commit d3b3387 hub+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 e4419b4 hub+prod (DEPLOY.md sync po help LP storage fix 4c19bb7; bez migrací)
  • 2026-05-16 07:05 UTC — commit fbe4897 hub+prod (3 URL routing hotfixy: 0bd1b2c navigate()+Link to= bug fix v bd4af36, 5767f05 logo→LP, f84cd28 @ai-guide headers cleanup; bez migrací)
  • 2026-05-16 06:17 UTC — commit e378fed hub+prod (CR-148 URGENT URL uklid: 3c54bff /app routing loop + footer fakturační údaje; migrace 109 FooterCompanyInfo)
  • 2026-05-16 05:55 UTC — commit 7eadc05 hub+prod (CR-148 LP fixes: descriptions "Jak to funguje" + outputs gap; migrace 108 LandingStepsDescriptionField)
  • 2026-05-16 04:53 UTC — commit 9b4dd17 hub+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: 148e41b6cfe392 (RLS fix mid-deploy) → 8f35d579b4dd177eadc05e378fedfbe4897e4419b4d3b3387
  • Pre-deploy prod: 8a7b7e5 (přímý skok přes 9b4dd17, hub validoval celý bundle)
  • 2026-05-15 12:41: 8a7b7e5 hub+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; runbook docs/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: false vždy (orthogonal: feedback_no_synchronize.md)
  • Build deps: slug migrace + diacritics fix (CR-121 amend) — pozor na typeorm migration:run při deploy
  • Sentry: VITE_SENTRY_DSN build 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).