Skip to content

Changelog — Odladěné problémy

Položky zde = nesmí se rozbít. Při jakékoliv změně v oblasti ověřit, že regrese nenastala.

2026-05-07

DEPLOY: billit hub+prod 9fa75ce — bundle cc81773..9fa75ce (CR-025 + REQ-043 + REQ-044 specs + api-keys fix)

  • Služba: Billit hub (15:55 CEST) + prod (16:09 CEST) — schválení po jednom (deploy ok billit hub, deploy ok billit prod)
  • Verze: cc817739fa75ce (master), 5 commitů
  • Změny:
  • 9fa75ce fix(billit-web): UX — full-form subject create dialog + click-to-copy API key
  • 8939d9b fix(billit-api): api-keys POST response shape — flatten {key, rawKey}{...entity, key: rawKey}
  • ecaef2e + a475bd7 docs(specs): REQ-044 update — Cortex payload schema
  • ddd117c docs(specs): REQ-044 draft — evidence přijatých faktur (received_invoices)
  • 0416e0d feat(billit): CR-025 + REQ-043 — orders security harden (BE) + webhook UI spec
  • Migrace: žádné (deklarováno db_change=migration ale No migrations are pending — billit deklaračně zachoval migration_from=0c1e2db, prakticky nic nového)
  • HC: billit.s60hub.cz/health + billit.cz/health 200 OK, kontejnery recreated, sentinel labels OK
  • Source hash: VERIFIED na obou env (9d688fe6… na prod)
  • Anomálie: první spuštění deploy.sh na hub i prod skončilo silent exit v [6/8] Docker deploy (žádný výstup, EXIT=1 přes set -euo pipefail); druhý pokus na obou pak prošel bez problémů. Stejný symptom jako 2026-05-03 první prod pokus. Příčina neznámá (pravděpodobně transient SSH/docker race), neblokující ale opakující se. TODO sentinelu: přidat retry logic nebo logování do [6/8] step.

2026-05-03

DEPLOY: billit hub+prod cc81773 — bundle 0c1e2db..cc81773 (CR-022 + CR-023 + REQ-042 banking)

T-20260502-042 → billit

  • Služba: Billit hub (2026-05-02 22:50 CEST) + prod (2026-05-03 06:38 CEST) — schválení po jednom (nejdřív DEPLOY OK billit hub, pak DEPLOY OK billit prod)
  • Verze: 1aa8deecc81773 (master)
  • Změny: CR-022 + CR-023 + REQ-042 (Fio sync metadata), banking-filter feature, REQ-042/CR-024 specs
  • Migrace: REQ042AddFioSyncMeta1772690000000 (typeorm) — ALTER TABLE bank_accounts — proběhla úspěšně na hub i prod
  • HC: billit.s60hub.cz/health + billit.cz/health 200 OK, kontejnery healthy s labelem cc81773
  • Source hash: VERIFIED na obou env po finálním deployi (659f54a3a58be9d3c476168812a1bd1a na prod, hub po fixu deploy.sh)

Diagnóza source hash mismatch (původně FAIL):

  1. Orphan /opt/billit/s60-auth-sdk/ na obou env — pozůstatek z doby před přechodem billit-web na vendored tarball (./vendor/s60-auth-sdk-0.1.0.tgz v package.json). Rsync --delete nemohl smazat (uvnitř .git/ sample hooks readonly).
  2. hub-alfa: jen .git/ (sample hooks), žádný build artifact
  3. prod-alfa: .git/ + reálné dist/*.{js,d.ts,json} z 11.3.2026 (24+ souborů) — funkčně dead, kontejner mounts [], SDK je v Docker imagi z buildu, ne mountovaný za běhu
  4. Fix: ssh root@<host> rm -rf /opt/billit/s60-auth-sdk na obou env. Před prod akcí proveden safety check find ... -type f -not -path '*/.git/*' (našel dist/), evidence ne-použití (mounts [], vendored tgz v package.json) ověřena → smazáno.

  5. .claude/settings.json v hash výpočtu deploy.sh — soubor je commitován v billit repu, ale rsync ho vylučuje (--exclude='.claude' v deploy.sh:614). Hash výpočet ho ale zahrnoval → trvalý mismatch (lokální verze ≠ stará remote verze).

  6. Fix v sentinel repu: přidán | grep -v '\.claude' do LOCAL_SRC_HASH/REMOTE_SRC_HASH find pipe (deploy.sh:901-903), aby exclude byl symetrický s rsync.
  7. Dopad: všechny budoucí deploy budou source hash count s .claude/ ignorovaným. Funkčně pro běh aplikace 0 — .claude/ je config Claude Code agenta, nikdy se nedeployuje.

Lekce: - Před rm -rf na remote <dir> vždy spustit find <dir> -type f -not -path '*/\.git/*' | head jako safety check (orthogonal feedback feedback_remote_rm_safety_check.md). - Rsync exclude listy musí být symetrické s hash výpočtem v deploy.sh; jakýkoli další exclude přidaný do rsyncu (__pycache__, .vscode, atp.) musí jít i do hash pipe.

CR-141 multi-tenant infra — DO Spaces, PG super_admin role, DNS migrace, DO PG cleanup

T-20260502-021/022/023, T-20260502-024/029/043 → pulse

1) PG super_admin role (3× per env, BYPASSRLS) na pg-alfa (Hetzner self-hosted PG): - s60_pulse_super_admin_dev + GRANTS na s60_pulse_dev - s60_pulse_super_admin_hub + GRANTS na s60_pulse_hub - s60_pulse_super_admin_prod + GRANTS na s60_pulse_prod - ALTER DEFAULT PRIVILEGES (future tables auto-granted) - Hesla: /root/secrets/pulse/.env.{hub,prod} jako SUPER_ADMIN_DATABASE_URL, dev creds doručeno přes Tailscale (T-20260502-024) - Existující users (s60_pulse_<env>_user) měly rolbypassrls=f už od defaultu — žádný ALTER potřeba

2) DO Spaces bucket s60-pulse-tenants + CDN endpoint: - Bucket vytvořen Liborem v DO control panelu (region fra1, Restrict File Listing ON, Enable CDN ON) - CDN endpoint id: a754b348-96aa-4bf3-b7b8-b80e3803a802 - Custom doména: cdn.pulselab.cz (Libor explicit rozhodnutí 2026-05-02 — pulse-platform branded, ne shared cdn.studio60.cz) - SSL: Let's Encrypt cert získán přes Certbot DNS-01 (CF API token), uploaded do DO jako custom cert (id 3d686908-...) - Scoped access key: RW jen na s60-pulse-tenants (DO801... prefix) - Creds: /root/secrets/pulse/.env.{hub,prod} jako DO_SPACES_* - CORS: blokované DO API (AccessDenied i s fullaccess key) — Libor doplní ručně přes control panel pokud potřeba - Smoke: PUT/GET/DELETE přes scoped key, fetch přes https://cdn.pulselab.cz/<file> 200 OK end-to-end

3) DNS infra (Forge přes Cloudflare): - super.pulselab.cz A → 178.104.36.211 (DNS only) - 5pd.cz migrace Wedos → CF (zone 70e52dea40177d2a6b3ed994c820d299), A @/CNAME www → 178.104.36.211 - 5pd.cz NSSET v CZ-NIC: WEDOS → CFNS-WEBSTER-1775625015 (paityn, todd... resp. bruce, yolanda dle account) - cdn.pulselab.cz CNAME → DO Spaces CDN endpoint (DNS only, kvůli DO custom doména routing) - Pulselab.cz nemá wildcard, studio60.cz má wildcard * → merlin (legacy, nedotýkat)

4) DO managed Postgres s60-postgres-dev SMAZÁN: - Cluster nebyl používán žádnou aktivní službou (všechny prod containers ověřeny → pg-alfa Hetzner) - 30 DBs, ~600 MB raw, 0 active connections per DB - Backup: /root/projects/sentinel/backups/do-postgres/20260502-145145/ (69 MB compressed pg_dump custom format, 30 DBs + roles inventory) - API DELETE → HTTP 204, cluster not_found. Úspora ~$15/měs.

5) DO DNS zóny SMAZÁNY: - s60dev.cz + s60hub.cz v DO DNS byly legacy balast — domény dlouho v Cloudflare (paityn/todd NS aktivní) - Backup: /root/projects/sentinel/backups/do-dns/*.json - API DELETE → HTTP 204, zone count 0

Sentinel scope ovedená rule: Sentinel commitoval ranější (pre-2026-04-27) deploy stamps do pulse repo (deploy(pulse): hub+prod ...). Toto chování bylo retired — sentinel nyní jen pulluje cizí repos, commit jen do s60-sentinel repo (orthogonal feedback_never_change_code.md enforcement).

2026-04-30

DEPLOY: pulse 5ff6982 — hub + prod (CR-095 + CR-129 + CR-132 follow-ups, žádné migrace)

  • Služba: Pulse hub + prod
  • Verze: 1e1f1b55ff6982 (T-20260430-010, 4 commity)
  • Změny (commit-by-commit):
  • 5ff6982 CR-132 follow-up — reopen tlačítko nad výstupem (Chat + Profile + Admin) + noCredits guard (toast + redirect /profile)
  • 7005ba9 CR-129 follow-up — request-context aware getBaseUrl(). AsyncLocalStorage, McpController obalí handler do requestContext.run(host, proto). Fix prod bugu kde MCP responses na pulselab.cz vracely .s60dev.cz URL (env var fallback). Mapping mcp.pulselab.cz → pulselab.cz, mcp.s60hub.cz → pulse.s60hub.cz
  • 332e39c CR-095 bugfix — admin viděl "1/21 metodik" místo "1/12" (canSeeHidden=true admin role pouštěl archived methodologies do client endpoint count). Fix: archived VŽDY filtrované v buildClientMethodologiesList, hidden categories zůstávají gatované přes canSeeHidden
  • 7481f4b NFR-007 — ESLint rule tests + CI gate + PR template (sentinel autor)
  • ec0b24b CR-139 — MCP templates E2E test script scripts/cr139-mcp-test.js (sentinel autor)
  • db_change=false — žádné DB migrace v této dávce, čistý code-only deploy
  • Hub: 5ff6982 VERIFIED, QA Gate RELEASE tier PASS 15/15 (smoke 2 + regression 13). Audit FAIL false-positive (DEPLOY.md db_change: migration z dřívější deklarace, ale 088 už applied → diff od b454919..5ff6982 nemá nové migrace; non-blocking).
  • Prod: 5ff6982 VERIFIED. Smoke OK:
  • pulselab.cz/health 200
  • /mcp 401 + WWW-Authenticate (regrese SSE blok)
  • /api/template/render/methodology-detail-v1/methodology/brand-identity 200 (CR-139 templates regrese)

2026-04-29

DEPLOY: pulse 1e1f1b5 — hub + prod (heroes Radka golden/iron/wood + SPEC.md fix)

  • Služba: Pulse hub + prod
  • Verze: 6e68e51 → hub b454919 → prod 1e1f1b5 (T-20260429-086, T-20260429-087)
  • Změny:
  • CR-093 follow-up — heroes Radka golden/iron/wood mapping
  • Migrace 1710000000088 HeroImagesRadkaGoldenIronWood:
    • hero_levels.imagePng UPDATE (3 rows): Spark→wood-level3, Bolt→iron-level3, Storm→golden-level3, Pulsar=NULL
    • hero_sublevels.imagePng UPDATE (12 rows): Spark→wood, Bolt→iron, Storm→golden, Pulsar=NULL
    • Idempotentní (UPDATE per name), down migration vrací staré spark/bolt/storm jména
  • 12 PNG souborů v gitu (specs/wireframes/hero/{wood,iron,golden}-level{0,1,2,3}.png, commit 4088a51)
  • Static serving přes /specs/hero/* (main.ts existsSync gate)
  • src/templates/SPEC.md (= fix TODO T-20260429-075 ze včerejšího CR-139 deploye → spec-compliance test pass, čistý průchod bez --skip-ci)
  • REQ-005 regression test extended (test/regression/auth/invite-token-req005-extended.spec.ts, commit 1e1f1b5 push během prod deploye, jen test soubor)
  • Hub: b454919 VERIFIED, QA Gate PATCH tier PASS 15/15 (smoke 2 + regression 13)
  • Prod: 1e1f1b5 VERIFIED, smoke OK
  • pulselab.cz/health 200
  • Migrace 088 applied
  • hero_levels updated (Bolt→iron-l3, Spark→wood-l3, Storm→golden-l3, Pulsar=NULL)
  • Static PNG files reachable: wood-level0.png 3.5MB, iron-level3.png 4.3MB, golden-level2.png 4.6MB (200 OK)
  • Bezchybný deploy — žádné migration permission issues, žádný spec-compliance fail, čistý průchod

DEPLOY: pulse 6e68e51 — hub + prod (CR-139 templates system)

  • Služba: Pulse hub + prod
  • Verze: e2966466e68e51 (T-20260429-074)
  • Změny: CR-139 — generic Handlebars region templates + 13 MCP tools + per-tenant CSS overlay
  • Commity: 76ebaa9 (CR-139 main), 6e68e51 (CR-139 follow-up: dynamic credit cost + public page refactor + visual check)
  • Migrace (2):
  • 1710000000086 CR139TemplatesSystem — vytvořila tabulky templates / template_content / tenant_theme_css + seed methodology-detail-v1 (10 regionů per Mišin návrh) + methodology-detail-legacy (markdown fallback) + brand-identity content z Mišina docx
  • 1710000000087 CR139MigrateMethodologiesToV1 — auto-migrace 11 metodik z content_blocks meta → 10 regionů v1 šablony (LEFT JOIN na methodology_metadata.creditCost — premium 3 / mid 2 / standard 1 dynamicky)
  • Idempotentní: CREATE TABLE IF NOT EXISTS, INSERT ... ON CONFLICT DO UPDATE, žádný gen_random_bytes (pulse aplikoval lekci z dopolední pgcrypto incident), gen_random_uuid jen built-in
  • CI bypass: Sentinel guard zachytil tests/spec-compliance/spec-structure.spec.ts fail (6 missing SPEC.md > práh 5; CR-139 přidal src/templates/ modul bez SPEC.md). Libor schválil --skip-ci. Pulse dostal TODO T-20260429-075 doplnit src/templates/SPEC.md.
  • Hub: 6e68e51 VERIFIED, QA Gate RELEASE tier PASS 15/15. Templates (2) + template_content + tenant_theme_css tables verified.
  • Prod: 6e68e51 VERIFIED, žádný incident, migrace prošly při container start. pulselab.cz/health 200, templates=2, template_content=23 records, tenant_theme_css table exists.

DEPLOY: pulse e296646 — hub + prod (CR-124 až CR-135 batch + JPG hero upload)

  • Služba: Pulse hub + prod
  • Verze: 63ae3abe296646 (kód commit a83fa98; T-20260429-046, T-20260429-052, T-20260429-055, T-20260429-056, T-20260429-057, T-20260429-060)
  • Scope (12 položek): CR-124 (invite link safety + Y3 deterministic welcome), CR-125 (ReviewPanel rewrite), CR-126 (Email PDF download box), CR-127 (Completed dialog fallback), CR-128 (admin mobile responsive), CR-129 (getBaseUrl helper + ContentEditor cleanup), CR-130 (methodology_demos kurátorský systém + 6 MCP tools), CR-131 (landing 3. CTA), CR-132 (reopen metodiky + audit log), CR-134 (JSONB pollution hotfix), CR-135 (hero images upload — JPG/SVG/PNG whitelist), NFR-007 (ESLint pulse/no-zod-any-in-boundary)
  • Migrace (4): 1710000000077-CR125ReviewPanelDisplayType ✅, 1710000000082-CleanupPollutedMethodologyVersions ✅ (cleanup 13 polluted methodology_versions na prod), 1710000000083-CR130MethodologyDemos ✅, 1710000000084-CR132ReopenAndStableUrl (selhala na prod, viz FIX: dál)
  • Hub: e296646 VERIFIED, smoke + regression PASS 15/15. Audit FAIL (false-positive: porovnává last_deployed..current diff, nové migrace už applied z předchozího 4f73c55 deploye → audit nevidí "nové migrace" v posledním diffu).
  • Iterace pulse push (důvod hodinového waste): pulse poslal deploy request, sentinel guard zablokoval (db_change: none v DEPLOY.md ale 4 migrace v diffu) → pulse aktualizoval DEPLOY.md, ale lint-staged hook vytvořil dodatečný commit (9fccd7d → 4f73c55) → sentinel guard pak rejected znovu protože pulse popsal změnu jen ve zprávě, ne v repo. Pulse pushl skutečný commit (4f73c55) → deploy proběhl. JPG support fix v e296646 (Libor zjistil že hero upload bere jen SVG/PNG) následoval jako další iterace. Lesson: vždy ověřit git ls-remote origin master po push (lint-staged může přidat commit nad ten který agent právě committed).
  • DB cleanup (CR-134): 13 řádků v methodology_versions na s60_pulse_prod vyčištěno — odstraněny numeric keys z JSONB content (slugy: brand-guide v2, brand-identity v3+v4+v5, competitor-analysis v2, content-creation v2, convert-to-sale v2, customer-path v2, feedback v2, idea-to-sale v3, pricing-authority v2, risk-radar v2, selling-web v2). "Remaining polluted: 0".

FIX: prod s60_pulse_prod — CREATE EXTENSION pgcrypto (drift mezi env)

  • Problém: Migrace 1710000000084-CR132ReopenAndStableUrl selhala na prod s permission denied to create extension "pgcrypto". Container zasekl v TypeORM init retry loop, prod down ~10 minut.
  • Root cause: Extension drift mezi env. s60_pulse_hub a s60_pulse_dev měly pgcrypto z dřívějška (manuálně nainstalovaná, nezdokumentovaná). s60_pulse_prod ji neměla. Migrace 084 obsahovala CREATE EXTENSION IF NOT EXISTS pgcrypto — na hub/dev no-op, na prod vyžadovalo superuser (kterým app user s60_pulse_prod_user není).
  • Fix: su - postgres -c "psql -d s60_pulse_prod -c 'CREATE EXTENSION IF NOT EXISTS pgcrypto'" (po Liborově DB OK pgcrypto)
  • Verze extensions po fixu: všechny tři env (hub/prod/dev) mají teď plpgsql + pgcrypto + uuid-ossp — drift odstraněn
  • Recovery: TypeORM se chytl při dalším retry (~10s), migrace 084 prošla, container start. Nest application successfully started, pulselab.cz/health 200, 78 stale credit holds released.
  • Runbook: docs/docs/runbooks/pg-extension-drift.md (preventivní postup pro budoucí migrace s CREATE EXTENSION)

DB COPY: s60_pulse_prods60_pulse_dev (diagnostické zrcadlo)

  • Žadatel: Libor (pulse agent diagnostika reálného stavu aplikace na devu)
  • Postup (lokálně na pg-alfa Hetzner, sentinel přes SSH):
  • Backup dev: /tmp/pulse-dev-backup-20260429-pre-prodcopy.dump (3.3M)
  • Dump prod: /tmp/pulse-prod-20260429.dump (5.7M, --no-owner --no-acl)
  • Terminate 5 idle dev connections
  • DROP DATABASE s60_pulse_dev + CREATE DATABASE OWNER s60_pulse_dev_user
  • pg_restore --no-owner --no-acl -d s60_pulse_dev z prod dumpu
  • Bug v 1. iteraci: Po pg_restore --no-owner měly všechny 30 tabulek + 1 sequence + schema public owner postgres (uživatel co spustil restore). Pulse migrace pak selhaly (ALTER TABLE vyžaduje owner, ne jen GRANT).
  • Fix: DO-block iterace ALTER TABLE/SEQUENCE/SCHEMA OWNER TO s60_pulse_dev_user pro všechny public objekty.
  • Verifikace: clients=32, sessions=135 (match prod). Owner po fixu: tables=30/sequences=1/schema=1, vše s60_pulse_dev_user.
  • GDPR: dev DB teď obsahuje reálné osobní údaje klientů. Pulse použít pouze pro diagnostiku.
  • Lesson: při pg_dump → pg_restore mezi env vždy doplnit ALTER OWNER krok (GRANT nestačí pro DDL operace).

2026-04-28

DEPLOY: pulse 63ae3ab — hub + prod (PDF page break wider coverage)

  • Služba: Pulse hub + prod
  • Verze: 3587e7263ae3ab (release commit b7f1407 + DEPLOY.md update; T-20260428-429)
  • Změny: @media print rule rozšířena (CSS-only fix v src/output/default-template.ts):
  • Širší break-inside: avoid listy (typo-samples, funnel-diagram, triangle-visual, shade-palette, gradient-library, type-scale-ladder, ui-component-preview, ui-card, personality-radar, archetype-compass, social-templates, social-item, takeaway, intro, ctx, outro-card)
  • widows: 3; orphans: 3 na body/p/li (min 3 řádky pohromadě)
  • ul/ol/table { break-inside: auto } (povolit dělení dlouhých seznamů → menší bílé mezery)
  • thead { display: table-header-group } (hlavička tabulky na každé stránce)
  • Důvod: Libor reportoval rozdělení bloku který by se neměl (brand-identity output 46ebaed5, str. 12-13)
  • db_change: none, migration: 0 pending
  • Ověření hub + prod:
  • QA Gate hub: PASS 15/15
  • Hub + prod 63ae3ab VERIFIED
  • Smoke (regrese): /api/content/chat 404 ✅, /mcp 401 ✅, /health 200 ✅

DEPLOY: pulse 3587e72 — hub + prod (CR-122 + CR-123 + PDF page break fix)

  • Služba: Pulse hub + prod
  • Verze: 686dd6c3587e72 (release marker 53c4e1a schválený Liborem; pulse pushnul +1 follow-up commit do masteru — DEPLOY.md update bez funkční změny; T-20260428-415)
  • Změny:
  • CR-122 Content Editor trim — POST /api/content/chat odstraněn (AI chat z admin UI pryč, MCP přebírá editaci)
  • CR-123 Edge stavy retry/finish — non-ACTIVE sessions vrátí current state místo BadRequestException. Řeší incident "Monika Webster" (retry na completed session → 400)
  • PDF page break fix (commit 53c4e1a) — @page margin sjednocen s Puppeteer/Gotenberg pdfOptions.margin (25/15/20/15mm). Text přetečený na další stránky respektuje horní/dolní okraje, nepřekrývá header/footer.
  • 4× already-merged: nginx /mcp (CR-116), GlitchTip DSN fix, TRIAL credits hold, CR-116 status implemented
  • db_change: none, migration: 0 pending
  • Pulse pre-deploy: 835/835 tests pass, TS clean (backend + admin + client), dev smoke OK
  • Ověření hub + prod:
  • QA Gate hub: PASS 15/15 (2 smoke + 13 regression: pulse-spec-session 12/12, webhook-validation 3/3)
  • Hub 3587e72 VERIFIED, prod 3587e72 VERIFIED
  • POST /api/content/chat → 404 (CR-122 regrese) ✅
  • POST /mcp → 401 + WWW-Authenticate (regrese SSE blok beze změny) ✅
  • GET /health → 200 ✅
  • POST /webhooks/billit → 400 (signature validation regrese) ✅

DEPLOY: auth 9b9a228 — hub + prod (DCR auto-approve rozšířen na confidential PKCE)

  • Služba: S60Auth hub + prod
  • Verze: b28d79c9b9a228 (auth schválil 9ee56e2, master pushnul 9b9a228 = 9ee56e2 + dcr.service.spec.ts unit test, žádná funkční změna; T-20260428-326)
  • Změny: DCR auto-approve rozšířen z token_endpoint_auth_method=none na celý grant authorization_code. Pokrývá nyní confidential klienty s client_secret_post (jak Claude Desktop posílá).
  • db_change: none
  • Ověření:
  • Hub QA Gate: PASS, 9b9a228 VERIFIED
  • Prod 9b9a228 VERIFIED
  • Live test prod: DCR s token_endpoint_auth_method=client_secret_post + scope mcp:toolsregistration_status=approved ✅ (předtím by bylo pending)
  • DB cleanup s60_auth_prod.systems (Libor schválil DB OK):
  • DELETE 3 řádky — pending Claude Desktop retry zombies (s60-claude-ffaa2195, s60-claude-d1b5a0b7, s60-claude-802bc023)
  • DELETE 3 řádky — sentinel testovací klienti (s60-sentinel-*)
  • Final SELECT na claude.ai/api/mcp/auth_callback: 0 rows (čisto pro fresh Claude Desktop registraci)
  • Sled bugů: B-1 (DCR dedup) → fixnuto v b28d79c. B-2 (auto-approve jen pro none) → fixnuto v 9b9a228. Sentinel scope kompletní.

DEPLOY: auth b28d79c — hub + prod (DCR dedup removed, auto-approve PKCE public)

  • Služba: S60Auth hub + prod
  • Verze: 336985ab28d79c (T-20260428-313, T-20260428-317)
  • Změny (auth strana):
  • DCR POST /api/auth/clients/register — odstraněna deduplication podle redirect_uri. Každý request vytvoří nový unikátní client_id.
  • DCR auto-approve pro token_endpoint_auth_method=none (PKCE public klienti).
  • db_change: none, migration: ne pending
  • Ověření:
  • QA Gate hub: PASS (smoke 2/2)
  • hub b28d79c VERIFIED (commit + source hash + sentinel labels)
  • prod b28d79c VERIFIED
  • DCR dedup test: 2 různé client_id pro stejné redirect_uri na hub i prod ✅
  • DB cleanup s60_auth_prod.systems (na žádost auth, schváleno Liborem):
  • DELETE FROM systems WHERE redirect_uris @> ARRAY['https://claude.ai/api/mcp/auth_callback'] AND oauth_client_id LIKE 's60-test-%'; → 1 řádek (s60-test-probe-30983d21)
  • DELETE FROM systems WHERE redirect_uris @> ARRAY['https://claude.ai/api/mcp/auth_callback'] AND registration_status = 'pending'; → 1 řádek (s60-claude-e411dff7, dnešní Claude Desktop pokus)
  • Final SELECT: 0 rows
  • Známý nedostatek (následný TODO auth, T-20260428-325):
  • Auto-approve NEpokrývá token_endpoint_auth_method=client_secret_post. Claude Desktop posílá confidential client + secret, ne PKCE public → DCR výsledek pending → token endpoint 403. Sentinel scope nepokrývá kód auth — čeká se na druhý fix od auth agenta.

FIX: pulse.s60hub.cz + pulselab.cz — Nginx /mcp SSE-friendly location (MCP Streamable HTTP)

  • Problém: Po opravě OAuth discovery (předchozí položka) login a redirect proběhly OK, ale Claude Desktop hlásil "not connected" — tichý fail bez errorů v logs ani GlitchTip.
  • Root cause: Hub i prod Nginx config neměl specifický location /mcp blok — request padal do generic location / s default proxy_buffering on a proxy_read_timeout 120s. Streamable HTTP transport (MCP spec 2025-11-25) drží connection a streamuje events; default buffering Nginxu drží initialize response v bufferu, klient ho nikdy nedostane.
  • Fix: Přidán location /mcp { ... } blok před generic location / na hub-alfa i prod-alfa s:
  • proxy_buffering off + proxy_cache off + chunked_transfer_encoding on (SSE)
  • proxy_read_timeout 3600s + proxy_send_timeout 3600s (long-lived connection)
  • proxy_set_header Connection "" (HTTP/1.1 keepalive)
  • Ověření:
  • curl -X POST https://pulse.s60hub.cz/mcp → 401 + WWW-Authenticate: Bearer realm="mcp", resource_metadata="https://pulse.s60hub.cz/.well-known/oauth-protected-resource"
  • curl -X POST https://pulselab.cz/mcp → 401 + analogická WWW-Authenticate ✅
  • Regrese: GET https://pulselab.cz/ → 200 (landing OK), /.well-known/oauth-protected-resource → 200
  • Backupy: pulse.s60hub.cz.bak.20260428-mcp (hub), pulselab.cz.bak.20260428-mcp (prod)
  • Dev (pulse.s60dev.cz na cerebro): mimo sentinel scope — TODO infra agent
  • Reportoval: pulse (T-20260428-299)

FIX: auth.studio60.cz — Nginx /.well-known/ proxy (MCP OAuth discovery)

  • Problém: https://auth.studio60.cz/.well-known/oauth-authorization-server vracel SPA HTML místo RFC 8414 JSON → Claude Desktop MCP připojení na pulselab.cz/mcp selhávalo na 3. kroku discovery flow
  • Root cause: Nginx config na prod-alfa (/etc/nginx/sites-enabled/auth.studio60.cz) měl jen exact-match location = /.well-known/openid-configuration s rewrite na /api/auth/oauth/openid-configuration. Ostatní /.well-known/* cesty padaly do location / → SPA index.html. Hub-alfa měl správně generic location /.well-known/ { proxy_pass http://127.0.0.1:3002; }.
  • Backend OK: S60Auth backend (CR-116 F1, deployed 2026-04-22) podporoval /.well-known/oauth-authorization-server i /.well-known/openid-configuration přímo na portu 3002 — chyběl jen Nginx route.
  • Fix: Sjednoceno s hub-alfa — location = /.well-known/openid-configuration blok nahrazen za location /.well-known/ s proxy_pass http://127.0.0.1:3002 (bez rewrite).
  • Ověření:
  • curl https://auth.studio60.cz/.well-known/oauth-authorization-server → JSON 200 ✅
  • curl https://auth.studio60.cz/.well-known/openid-configuration → JSON 200 ✅ (regrese OK)
  • Backup: /etc/nginx/sites-enabled/auth.studio60.cz.bak.20260428 (na prod-alfa)
  • Reportoval: pulse (T-20260428-284 QUESTION)

2026-03-26

FEAT: Pre-deploy snapshots v deploy.sh (ADR-007)

  • Co: Každý deploy automaticky vytvoří snapshot (app.tar.gz + db.sql.gz + env.backup + manifest.json)
  • Kde: /var/backups/deploy-snapshots/<project>/<env>/<timestamp>-<commit>/
  • Rotace: <24h all, 1-7d daily, 7-30d weekly, >30d delete (cron 02:00 na hub+prod)
  • Rollback: ./deploy.sh <project> <env> --rollback latest nebo --rollback <snapshot-id>
  • Otestováno: Pulse hub dry-run — snapshot 2.1MB (app 1.9M + DB 192K)
  • Motivace: Ztráta Pulse methodology dat kvůli rsync --delete

DEPLOY: Pulse 3aaa1c2 — hub + prod (bugfixy + debug API + migration:run)

  • Služba: Pulse hub + prod
  • Verze: 0593344 → 3aaa1c2
  • Změny: metodiky v kategoriích bez active version fix, debug API endpointy, migration:run npm script, data-source.ts, CR-014 spec
  • Migrace: deploy.sh migration_command funguje (npm run migration:run OK, no pending)
  • Ověření: Health 200, migration OK
  • QA: Hub 10 PASS / 2 FAIL (PDF size + empty session — pre-existing)

DEPLOY: Pulse 0593344 — hub + prod (slug migrace + diacritics fix)

  • Služba: Pulse hub + prod
  • Verze: a81febc → 0593344 (1 commit)
  • Změny: Migrace 1710000000015-FixDiacriticSlugs — přegeneruje špatné slugy v clients + methodology_categories
  • Migrace: Proběhla automaticky (migrationsRun:true), deploy.sh migration_command stále failuje (chybí npm script)
  • Ověření: Health 200, migrace potvrzena v DB na hub i prod, slugy opravené

DEPLOY: Pulse a81febc — hub + prod (slug fix + synchronize disabled)

  • Služba: Pulse hub + prod
  • Verze: 8ce2887 → a81febc (3 commity)
  • Změny: methodology.controller slug fix (NFD normalize), synchronize:false, slugify regression testy
  • Migrace: žádná nová (migration:run script chybí v package.json — TODO pulse)
  • Ověření: Health 200 na hub i prod, summaryPdfPath error vyřešen (ALTER COLUMN)
  • QA: Hub 11 PASS / 1 FAIL (PDF size — pre-existing)

FIX: Pulse summaryPdfPath column mismatch — hub

  • Problém: Migrace vytvořila summary_pdf_path (snake_case), entity čekala summaryPdfPath (camelCase) → 500 error na outputs
  • Root cause: synchronize: true na hubu vytvořil sloupce v camelCase, migrace pak přidala snake_case duplikát
  • Fix: ALTER TABLE RENAME COLUMN na hubu; prod byl OK
  • Systémový fix: deploy.sh nyní spouští migration_command z deploy.yml; TODO pulse: odstranit synchronize:true

DEPLOY: Pulse 8ce2887 — hub + prod

  • Služba: Pulse hub + prod
  • Verze: 7634e01 → 8ce2887 (6 commitů, efektivně 2 změny)
  • Změny: slugify strips diacritics (admin + backend), Safari layout reverty (net zero), CLAUDE.md Relay URL update
  • Migrace: žádná
  • Ověření: Health 200 na hub i prod (:3100/health)
  • QA: Hub 11 PASS / 1 FAIL (progress endpoint 500 — pre-existing), prod --skip-ci
  • Poznámka: Jest ESM failures (6 suites, @exodus/bytes) — TODO poslán pulse agentovi

2026-03-24

DEPLOY: Pulse 7634e01 — hub + prod (CR-013)

  • Služba: Pulse hub + prod
  • Verze: 541c2a7 → 7634e01 (4 commity)
  • Změny: CR-013 Kategorie UX redesign + visibility flag, EntityNotFoundError→404 fix
  • Migrace: 1710000000012-AddCategoryVisible (ALTER TABLE methodology_categories ADD COLUMN visible)
  • Ověření: Health 200 na hub i prod (:3100/health), migrace ověřena na obou DB
  • QA: Hub QA gate pre-existing failures (summaryPdfPath, Venom down), CR-013 OK. Prod --skip-ci (pre-existing test failures)

DEPLOY: Pulse 541c2a7 — hub + prod

  • Služba: Pulse hub + prod
  • Verze: c33d32c → 541c2a7 (18 commitů)
  • Změny: CR-001 credit ledger + tier→subscription, CR-007 summary PDF, CR-008 workshop split-screen, CR-009 categories + prerequisites, CR-010 web URL analysis, CR-011 project-first UI, CR-012 gamification (badges, certificates), spec-compliance testy (31), TS build fix
  • Migrace: ano (1710000000014-GamificationTables + další)
  • Ověření: Health 200 na hub i prod (:3100/health)
  • QA: QA gate false positive (špatné URLs v testech), QA agent opravil testy

DEPLOY: BillIt debe805 — hub + prod

  • Služba: BillIt hub + prod
  • Verze: 4fea85f → debe805 (1 commit)
  • Změny: CR-008 — XLSX export, subjectId filter, async export >1000
  • Migrace: ne
  • Ověření: Health 200 na hub i prod (billit-api :3200, billit-web :3201)
  • QA: skipped (pulse testy false positive, QA agent timeout)

2026-03-23

DEPLOY: BillIt 4fea85f — hub + prod (T-20260323-001, T-20260323-004)

  • Služba: BillIt hub + prod
  • Verze: bcd8d32 → 4fea85f (3 commity)
  • Změny:
  • 1b2e3d9 CR-001–011: 11 CR implementací (Stripe idempotence, ARES cache+DPH, Stats cache, Recurring dedup, PDF branding, Email tracking, Exports XLSX, Admin defaults, CSV import, Webhooks 5 retries)
  • 8a2f646 DEC-009: BankEmailConsumer (BullMQ worker pro bank-email-inbound)
  • 4fea85f npm audit fix (HIGH vulnerabilities)
  • Migrace: ano (stripe_events + email_logs tabulky)
  • Ověření: Health 200 na hub i prod (billit-api :3200, billit-web :3201)
  • QA: skipped

2026-03-22

DEPLOY: Pulse c33d32c — hub + prod (T-20260322-076)

  • Služba: Pulse hub + prod
  • Verze: bfe405b → c33d32c (1 commit)
  • Změny: ACTIVE sessions visibility fix — findResumableByClient vracela jen PAUSED, nyní ACTIVE + PAUSED
  • Důvod: URGENT — klienti neviděli rozpracované sessions (Helena 6 sessions, 4 ACTIVE)
  • Ověření: Health 200 na hub i prod
  • QA: skipped (urgent bugfix, QA agent neodpověděl)

SECURITY: Odebrán cerebro SSH klíč z hub-alfa + prod-alfa (T-20260320-122)

  • Služba: hub-alfa, prod-alfa (authorized_keys)
  • Příčina: Auth agent z cerebra nasadil 7236286 přímo na prod BEZ sentinelu, BEZ QA gate, PŘED hubem. Cerebro SSH klíč (root@cerebro) byl v authorized_keys na obou serverech → jakýkoli agent na cerebru měl root přístup k produkci.
  • Fix: sed -i odebrání cerebro klíče z /root/.ssh/authorized_keys na hub-alfa i prod-alfa
  • Ověření: Sentinel SSH OK, cerebro → prod connection refused
  • Zbývající klíče: 2 na každém serveru (sentinel + Libor)

DEPLOY: Pulse 4462e58 — hub + prod (T-20260320-122)

  • Služba: Pulse hub + prod
  • Verze: 6c99b82 → 4462e58 (2 commity)
  • Změny: REQ-029 readonly debug API s dvouvrstvou autorizací, REQ-017 pricing integration testy
  • Ověření: Health 200 na hub i prod

DEPLOY: Pulse 6c99b82 — hub + prod (T-20260320-122)

  • Služba: Pulse hub + prod
  • Verze: 10c0b88 → 6c99b82 (~50 commitů)
  • Změny: Discord removal (DEC-005), Freelo escalation (REQ-026), cancel webhook (REQ-018), pause/resume (REQ-003), PDF print CSS fix, DB migrace (projects, missing columns, billitInvoiceId), QA readonly user, regression testy, spec catalog
  • Blocker: Migrace CreateQaReadonlyUser vyžadovala CREATE ROLE — provedena ručně přes doadmin na hub i prod, zapsána do TypeORM migrations tabulky
  • Readiness warn: deploy.yml hledá AUTH_URL/AUTH_CLIENT_ID/AUTH_CLIENT_SECRET, kód používá S60_AUTH_* prefix — obojí funguje (známý problém)
  • Ověření: Health 200 na hub i prod

2026-03-20

FIX: Pulse QA user — missing role, tier, credits (T-20260320-122)

  • Služba: S60Auth hub + Pulse hub (s60_auth_hub, s60_pulse_hub)
  • Příčina: QA user qa-pulse@s60dev.cz se prihlasil pres S60Auth (access OK), ale nemel zadnou roli v user_system_roles → JWT obsahoval rolesBySystem: {} → Pulse auth guard vracel 401 na vsechny authenticated endpointy. Navic v Pulse DB clients tabulce mel tier: NULL a creditsRemaining: 1.
  • Fix (hub):
  • INSERT INTO user_system_roles (user_id, system_id, role_name) VALUES ('<qa-user-id>', '<pulse-system-id>', 'client') v s60_auth_hub
  • UPDATE clients SET tier = 'triple', "creditsRemaining" = 100 v s60_pulse_hub
  • Prod: QA user jeste neexistuje — musi se nejdriv prihlasit, pak pridat roli + tier
  • Pulse system_id: 15912518-c5f1-4926-bf6e-b16948d4c585 (stejny hub i prod)
  • Overeni: QA user se musi znovu prihlasit pro novy JWT s rolemi

INFRA: Gotenberg PDF microservice — hub + prod (T-20260320-092)

  • Služba: Gotenberg (gotenberg/gotenberg:8) na hub-alfa + prod-alfa
  • Změna: Nový kontejner s60-gotenberg v /opt/gotenberg/docker-compose.yml, port 127.0.0.1:3050→3000, s60-network
  • Nginx: pdf.s60hub.cz (hub-alfa), pdf.studio60.cz (prod-alfa) — Tailscale IP whitelist (100.64.0.0/10), deny all
  • SSL: Certbot, auto-redirect HTTP→HTTPS
  • Pulse integrace: GOTENBERG_URL=http://s60-gotenberg:3000 v .env (Docker network, ne veřejná URL)
  • Fallback: Pulse padá na lokální Puppeteer pokud GOTENBERG_URL není nastaveno
  • Důvod: Izolovaný Chromium pro PDF generování, stabilnější než in-process Puppeteer

DEPLOY: Pulse 2a8a75e — mobile UX fix (T-20260320-092)

  • Služba: Pulse hub + prod
  • Commity: 2e457a4, 931f60d, bc9eb9b, f9ed892, 2a8a75e
  • Změny: Cookie session 1h→30d (mobilní klientky), scroll fix (wake/tab switch), textarea autoComplete off, Gotenberg PDF integrace, feedback mailto fallback
  • Důvod: Klientky na mobilu ztrácely session, UX problémy

DNS: s60hub.cz — převzetí zóny na hub-alfa (T-20260320-085)

  • Změna: Přesměrováno *.s60hub.cz, s60hub.cz, www.s60hub.cz z 164.90.182.148 (starý server) na 178.104.40.167 (hub-alfa). Smazáno portal.s60hub.cz, wiki.s60hub.cz.
  • Důvod: Sentinel přebírá DNS správu pro hub + prod. Celá zóna s60hub.cz nyní ukazuje na hub-alfa.
  • Stav: 11 A záznamů, vše → 178.104.40.167

CONFIG: Pulse nginx — odebrán location /outputs/ block (T-20260320-063)

  • Služba: Pulse prod (pulselab.cz, prod-alfa)
  • Změna: Odebrán location /outputs/ { alias /var/www/public/pulse/; } z /etc/nginx/sites-enabled/pulselab.cz
  • Důvod: Po deployi 6e9ba7d NestJS servíruje /outputs/:hash/:slug.html z DB. Nginx location block měl vyšší prioritu a blokoval NestJS. Hub-alfa tento block neměl.
  • Ověření: health 200, /outputs/ vrací 404 z NestJS (ne 403 autoindex off)

FIX: Pulse dev DB ownership — rename + dedicated user (T-20260320-019)

  • Služba: Pulse dev (pulse.s60dev.cz, cerebro)
  • Příčina: TypeORM synchronize padal — "must be owner of table sessions". Tabulky patřily doadmin, app běžela jako s60_infra_user
  • Fix: Vytvořen s60_pulse_dev_user, DB přejmenována s60_pulses60_pulse_dev, ownership všech tabulek/sekvencí/schématu převeden na nového usera
  • Ověření: curl http://127.0.0.1:3100/health{"status":"ok","db":"ok"}
  • Credentials: /root/secrets/pulse/.env.dev.db

2026-03-18

CONFIG: BillIt systems record — access_policy=open, default_roles={user}

  • Služba: s60-auth (hub + prod)
  • DB: s60_auth_hub, s60_auth_prod — tabulka systems
  • Změna: access_policy restricted→open, managed_by admin→api, default_roles →{user}
  • Důvod: BillIt je SaaS — každý přihlášený uživatel dostane auto-přístup s rolí 'user'
  • Požadavek od: auth agent

FIX: Auth CORS — custom domény blokovány

  • Služba: s60-auth-backend (prod + hub)
  • Příčina: CORS whitelist v main.ts povoloval jen *.s60dev/hub/studio60.cz
  • Fix: Přidány billit.cz, pulselab.cz, learnia.cz, kvantovaterapie.cz do allowedOrigins
  • Ověření: Billit login flow funguje, token exchange projde
  • POZOR: Fix je přímo na serverech, auth agent musí commitnout do repo

FIX: Auth missing DB columns — marketing_html, purchase_url, is_favorite

  • Služba: s60-auth-backend (prod + hub)
  • Příčina: Auth agent přidal @Column do entit bez TypeORM migrace
  • Fix: ALTER TABLE systems ADD COLUMN marketing_html, purchase_url; ALTER TABLE user_system_access ADD COLUMN is_favorite
  • Ověření: getPortalSystems endpoint vrací seznam aplikací bez QueryFailedError

FIX: Billit prod redirect na s60dev.cz

  • Služba: billit-web (prod)
  • Příčina: Chybějící VITE_AUTH_CLIENT_ID/SECRET/REDIRECT_URI/PUBLIC_URL v .env.prod → fallback na dev defaults
  • Fix: Přidány VITE vars do /root/secrets/billit/.env.prod, opraven home_url v auth DB, rebuild
  • Ověření: Žádné s60dev reference v buildu, login flow správně redirectuje na billit.cz

FIX: Billit auth DB home_url

  • Služba: s60_auth_prod.systems
  • Příčina: BillIt system record měl home_url=https://billit.s60dev.cz
  • Fix: UPDATE systems SET home_url='https://billit.cz' WHERE name='BillIt'

2026-03-15

FIX: Pulse HEALTHCHECK port 3200→3100

  • Služba: s60-pulse (hub + prod)
  • Příčina: Dockerfile HEALTHCHECK cílil na port 3200, app běží na 3100
  • Fix: Změna v Dockerfile na hub+prod, rebuild
  • Ověření: Container healthy, HTTP 200 na /health

FIX: billit-web HEALTHCHECK IPv6

  • Služba: billit-web (hub + prod)
  • Příčina: HEALTHCHECK používal localhost (resolves to ::1 IPv6), nginx jen IPv4
  • Fix: localhost→127.0.0.1 v Dockerfile
  • Ověření: Container healthy

FIX: PG backup Argus — chybějící IP + heslo

  • Služba: Argus backup cron
  • Příčina: DO PG trusted sources reset bez Argus IP + doadmin heslo změněno
  • Fix: Přidána Argus IP 37.205.14.239, aktualizován PGPASSWORD + PGSSLMODE=require
  • Ověření: 12/12 DB backup OK

FIX: deploy.sh — docker compose down zbytečný downtime

  • Služba: deploy/deploy.sh
  • Příčina: deploy.sh dělal docker compose down && up -d → zbytečný downtime
  • Fix: Odstraněn down, používá se up -d --build / --force-recreate

FIX: deploy.sh — QA gate arg order + path

  • Služba: deploy/deploy.sh
  • Příčina: qa-gate.sh volaný se špatným pořadím argumentů + špatná cesta
  • Fix: Opraveno na qa-gate.sh $ENV $SERVICE --tier $QA_TIER, cesta na /root/projects/qa/qa-gate.sh

2026-03-29 — billit 8771b10 (CRITICAL)

Commit: 8771b10 — fix(billit): email-first matching in tenant_users
Env: hub + prod
Status: ✅ DEPLOYED
Migration: none (db_change: none)

Změny: - tenants.service.ts: findAll email-first, user_id jako fallback - KnownUserInterceptor: auto-fix stale user_id na matching email - KnownUserInterceptor: auto-fix placeholder emails na matching user_id

Dopad: Liborovy tenanty se automaticky opraví při přihlášení.
Health: billit-api + billit-web 200 OK na obou serverech.

2026-03-30 — billit 83c9835 (hub + prod)

  • Commit: 83c9835 — fix(billit): add missing migration for enabled_payment_methods + auto_emails
  • Migration: 1772410000000-AddTenantPaymentMethodsAndAutoEmails
  • ALTER TABLE tenants ADD COLUMN IF NOT EXISTS enabled_payment_methods JSONB DEFAULT '["bank","card"]'
  • ALTER TABLE tenants ADD COLUMN IF NOT EXISTS auto_emails JSONB DEFAULT '{...7 events...}'
  • Hub: deployed + migrace OK, health 200
  • Prod: deployed + migrace OK, health 200
  • Poznámka: hub DB migrations table byla prázdná (0 záznamů) — vloženy fake záznamy pro 16 předchozích migrací před spuštěním nové