Spec: docs-portal — entitlement-gated produkční dokumentační portál¶
Status: NÁVRH v1 (2026-07-01, max/ultrathink). Rozšiřuje/nahrazuje
platform-docs.md(docs systém) +docs-production-deploy.md(prod deploy) + návrh 3-publikového portálu (auto-nav apps). Spec-first: review → ~95 % shoda → implementace.Autor kontext: Michal chce (1) docs na PRODUKCI (
docs.avaxis.cz) robustně — ať se to „zase někde nezamotá" (viz outage níže), (2) uživatel vidí PLNOU dokumentaci jen k software, který má (koupený/přiřazený); u ostatních jen info co umí + poznámka že plná dokumentace se zobrazí po zakoupení; NE all-or-nothing na celou platformu.
0. Kontext & poučení z outage¶
Dnešní stav (2026-07-01): jeden MkDocs (avax-platform/mkdocs.yml, Material, česky),
servírovaný z avaxdev host nginx (avax-docs vhost server_name docs.avaxdev.local
docs.avaxis.cz → /srv/avax-docs). Build avax-docs-build.timer (5 min). Vendor docs
pull-sync (pull_vendor_docs.py) + metadata sync (sync_app_docs.py). Auth-gate zárodek:
gen_docs_auth_nginx.py → avax-docs-auth.conf (dnes gateuje 9 paths).
🔴 Outage (28 dní, silent): dev1→dev2 migrace ztratila 4 FS objekty (ne kód):
/srv/avax-docs, /var/lib/avax-docs, avax-owned /var/log/avax-docs-build.log, symlink
/usr/local/bin/avax-docs-rebuild.sh. Build padal 41506× (226/NAMESPACE + set -e na
nezapisovatelném logu), site prázdný, docs.avaxis.cz externě mrtvý (NPM ho navíc vůbec nezná).
Poučení = návrhové cíle: (a) žádný netrackovaný host-stav (FS dirs/symlinky/ownership,
co migrace/rebuild ztratí), (b) idempotentní provisioning, (c) healthcheck + alert (tichý
28denní výpadek nesmí být možný), (d) reprodukovatelný build+deploy (CI, ne ruční).
Proxy realita: *.avaxis.cz routuje NPM (nginx-proxy-manager 2.11.1, proxy-app-1
@ 192.168.1.24, UI/API-managed). docs.avaxis.cz v NPM chybí (proto externě down).
1. Cíl¶
- Produkční
docs.avaxis.cz— robustní, reprodukovatelný, monitorovaný. - Jeden portál, 3 publika (taby): Uživatelská / Programátorská / Admin (superadmin).
V každém levé kolaps menu: Platforma (cross-cutting) + Aplikace/
(auto-nav všech apps). - Entitlement-gated viditelnost (jádro tohoto specu): plná dokumentace app jen pro toho, kdo app má; jinak teaser + upsell; per-app, ne na celou platformu.
2. Model viditelnosti (autoritativní matice)¶
Rozhodnutí per HTTP request = funkce (identita z JWT, tab/publikum, scope). Scope:
platform (obecné) | app:<slug>:teaser | app:<slug>:full | admin.
| Kdo \ Co | Platforma (user/dev) | App teaser (co umí + upsell) | App plný obsah | Admin tab |
|---|---|---|---|---|
| Anonym (bez JWT) | login redirect¹ | login redirect¹ | login redirect | login redirect |
| Přihlášený, app NEMÁ | ✅ | ✅ teaser | ❌ 403 → teaser/upsell | ❌ |
| Přihlášený, app MÁ (entitled) | ✅ | ✅ | ✅ plná | ❌ |
| super_admin | ✅ | ✅ | ✅ (vše) | ✅ |
¹ OTEVŘENÁ OTÁZKA A: teasery jako marketing chtějí být veřejné (bez loginu) → konflikt s „celý web za login". Doporučení: teaser = veřejný (láká k nákupu), plný obsah + Programátorská + Admin = za login + entitlement. Viz §10.
„Entitled" = má app ≙ stejná logika jako launcher katalog (single source of truth):
company má aktivní company_subscriptions / app_assignments na <slug> A projde
apps.required_roles gate (viz reference_role_gate). Docs viditelnost = katalogová
viditelnost → žádný duplicitní entitlement kód, reuse existující catalog service.
3. Architektura — statický MkDocs + edge auth_request¶
Zvolený model: MkDocs builduje VŠE (plný obsah) → statický site; reverzní proxy
(nginx / NPM) gateuje per-path přes auth_request na backend. Per-user rozhodnutí na edge.
internet → docs.avaxis.cz (NPM :443, SSL)
→ prod nginx (docs) ── auth_request ──▶ api.avaxis.cz /v1/docs/access?app=<slug>&audience=<>
│ 200 → servíruj statickou stránku z built site
│ 401 → redirect na login (api.avaxis.cz/login?redirect=…)
└ 403 → error_page → teaser dané app (public) / upsell (žádný únik obsahu)
Teaser-as-index (klíčový trik): …/apps/<slug>/index.md = teaser (public: co app umí +
CTA). Hlubší stránky (prvni-kroky, funkce/*, faq, dev/admin) = gated. Tím:
- menu ukazuje všechny apps (teaser vždy) → browsable ✅
- klik do hloubky u neentitled → 403 → upsell ✅
- „info co umí" (teaser) + „plná dokumentace po zakoupení" (gated) + per-app ✅
Proč ne alternativy:
- Dynamický docs backend (render per request) — ztratí MkDocs statiku/search/styl, těžké.
- Pre-built varianty (public/full) — nedělá per-USER (entitlementy se liší per company), kombinatorika.
- Static + edge auth_request — zachová statiku + search + per-user + výkon; precedens už je
(gen_docs_auth_nginx.py path-gate + docs-production-deploy.md auth_request vzor).
4. Auth + entitlement endpoint¶
- JWT cookie
avax_token(Domain=.avaxis.cz; HttpOnly; Secure; SameSite=Lax) — set/auth/login(sdílený meziapiadocs). Bez toho auth_request nemá identitu. - nginx (prod docs):
(auth_request cache pro výkon — TTL ~30–60 s, klíč = cookie+path.)
# per-app gated paths location ~ ^/(user-guide|dev)/apps/(?<docslug>[^/]+)/(?!index\.html|$) { auth_request /__docs_auth; auth_request_set $x $upstream_http_x_reason; error_page 401 = @login; error_page 403 = @teaser; try_files $uri $uri/ $uri.html =404; } location ~ ^/admin/ { auth_request /__docs_auth_admin; error_page 401=@login 403=@denied; ... } location = /__docs_auth { internal; proxy_pass https://api.avaxis.cz/v1/docs/access$is_args$args; proxy_set_header Cookie $http_cookie; proxy_pass_request_body off; } location @teaser { rewrite ^/(user-guide|dev)/apps/(?<s>[^/]+)/.*$ /user-guide/apps/$s/ redirect; } location @login { return 302 https://api.avaxis.cz/login?redirect=$scheme://$host$request_uri; } - Backend NEW
GET /v1/docs/access?app=<slug>&audience=<user|dev|admin>: - Validuj
avax_token(RS256/JWKS). Neplatný → 401. audience=admin→ jensuper_admin→ jinak 403.scope=platformnebo teaser → 200 (přihlášený).app:<slug>full → reuse catalog entitlement: má user.company<slug>? → 200 / 403 (+X-Reason: not-entitled).audience=devapp → OTEVŘENÁ OTÁZKA C (entitled zákazník vs jen platform-dev/vendor).- Endpoint je lightweight (jen entitlement lookup, cache-friendly), NE těžký.
5. Teaser (neentitled pohled)¶
- Obsah teaseru: název, „co umí" (capabilities z
connector.jsonprovides + app metadata), kategorie, případně screenshot; CTA: „Plná dokumentace k dispozici po zakoupení → [Obchod v launcheru]". - Zdroj: auto-generováno z app metadata (
sync_app_docs.py→docs/apps/<slug>.md) + connector capabilities. Rozšířit generátor: vyrobapps/<slug>/index.mdteaser (pokud vendor nedodá vlastní). Vendor může teaser přepsat (marketingový popis). - Nikdy neúnik plného obsahu v teaseru (oddělené soubory; teaser = jen index).
6. Struktura & nav (z 3-publikového návrhu)¶
- 3 taby publik (
navigation.tabs), levé menu Platforma + Aplikace/, kolaps (vypnout navigation.expand), auto-nav (mkdocs-awesome-pages-plugin: per-app.pages). - Per-app 3 flavory:
user-guide/apps/<slug>/(user) +dev/apps/<slug>/(programátorská) +admin/apps/<slug>/(admin). Vendor je generuje (handoffdocs/dev/app-docs-handoff.md),pull_vendor_docs.pytáhne všechny 3. - Všechny apps v menu (teaser) → „mít menu a projít" splněno.
7. Produkční hosting (robustní — anti-outage)¶
Princip: žádný netrackovaný host-stav. Dvě varianty (rozhodnutí §10-D):
- B1 — Kontejnerizovaný docs (doporučeno): docker image = nginx + built site (+ auth_request
konfig). Build v CI → image → deploy na prod (VM
192.168.1.20, pozn. docker tam zatím není = předpoklad) nebo na NPM/host. Kontejner = zero host-FS fragilita (co nás kouslo). Restart = čistý stav. Healthcheck v image. - B2 — Host nginx + idempotentní provisioning: zachovat avaxdev/prod nginx, ale
systemdExecStartPre=mkdir -pvšechny ReadWritePaths + log soubor (auto-heal migrace ztrát) +ConditionPathExistsalert. Levnější, ale stále host-stav.
Společné (obojí):
- CI .gitea/workflows/docs.yml: mkdocs build --strict (fail fast na broken nav/links) →
deploy (image push / rsync) → smoke gate (curl 200 hlavní + gated 401/403 + teaser 200).
- NPM: proxy host docs.avaxis.cz → docs upstream + SSL (NPM Let's Encrypt) + auth
(auth_request, ne jen Access List — kvůli entitlementu). Přidat přes NPM UI/API (pozn.:
ruční edit /data/nginx/proxy_host/*.conf NEfunguje, NPM regeneruje z DB → nutné UI/API +
NPM admin creds, viz §10-E).
- 🔔 Healthcheck + alert: externí monitor https://docs.avaxis.cz/ 200 (+ cert expiry) →
alert (chat/e-mail). Tichý výpadek = zakázaný.
8. Bezpečnost (ultrathink nálezy)¶
- 🔴 Search-index leak (kritické): MkDocs Material generuje statický
search/search_index.jsonobsahující text VŠECH stránek → neentitled uživatel by fulltextem přečetl gated obsah! Řešení (rozhodnout §10-B): (a)search_index.jsongateovat stejně jako obsah + client search jen nad povolenými; (b) build oddělené indexy (public/teaser vs per-entitlement) — složité; (c) backend search (server-side, entitlement-aware) místo statického. Doporučení: (a) minimálně gateovat search_index + teaser-only public index. - Admin tab (deploy/M2M/S3 secrets) = super_admin only → proto plný obsah za login (Michal OK).
- JWT RS256/JWKS validace; cookie
Secure+HttpOnly; auth_request endpoint interní (ne public). - Teaser bez úniku (oddělené soubory od plného obsahu).
- Rate-limit login redirect; auth_request cache neukládá napříč uživateli (klíč vč. cookie).
9. Fáze¶
- Prod robustní hosting — kontejner/idempotent + CI
docs.yml(build strict + smoke) + NPM proxy hostdocs.avaxis.cz+ SSL + healthcheck/alert. (Bez entitlementu: zatím celý web za login, nebo veřejné dokud není gate.) - Entitlement gate — backend
/v1/docs/access(reuse catalog entitlement) + nginx auth_request per app-path + admin gate. - Teaser — generátor teaser index z metadata/capabilities + search-index leak fix.
- 3-publikový obsah + vendor handoff — 3 flavory per app,
pull_vendor_docs.py3-flavor, apps si generují (handoff).
10. Rozhodnutí (zamčeno 2026-07-01)¶
- A. ✅ Teaser VEŘEJNÝ (Michal): nepřihlášený vidí teaser (co app umí + „plná dokumentace po zakoupení") = marketing. Plný obsah + Programátorská + Admin = za login + entitlement. → search index musí být teaser-only public (viz B), jinak leak.
- B. ✅ Search = gateovat
search_index.json(default): public search jen nad teaser/platform; plný obsah v odděleném indexu za auth_request (nebo backend search). Bez toho A leakuje. - C. ✅ Programátorská per-app = entitled (zákazník co app má, kvůli integraci) + platform-dev/vendor.
- D. ✅ Hosting = KONTEJNER na prod VM
192.168.1.20(Michal). Docker tam zatím není → instalace. nginx+built-site+auth_request v image; CI build → deploy na .20; NPM → .20. Zero host-FS fragilita. - E. NPM routing — docs.avaxis.cz → .20; přidání proxy hostu + SSL: NPM creds (UI/API) nebo příprava kroků / DNS. (Otevřené: creds path.)
- F. ✅ Granularita = per-app (subscription/assignment); jedna docs verze per app (ne per-channel).
Související¶
platform-docs.md(docs systém),docs-production-deploy.md(prod runbook, částečně obsoletní),dev/vendor-docs-sync.md,dev/help-deep-links.md,reference_role_gate, catalog entitlement service.