apps-gateway — specifikace¶
Status: draft Verze spec: 0.1 Aktualizováno: 2026-05-14 Související:
per-app-container.md,backend-deploy.md,server-infrastructure.md
1. Cíl¶
apps-gateway je tenký Python/FastAPI reverse proxy, který routuje requesty /apps/<slug>/* od klientů (launcher, web) na konkrétní app kontejnery. Sedí mezi vm-gateway Nginxem (TLS termination) a app kontejnery na avaxdev / vm-api.
Co dělá:
1. Discovery aplikací z apps DB tabulky (admin DB v core-api PG)
2. Routing s prefix strip: /apps/legal/items → http://app-legal:8101/items
3. Per-firma channel routing (alpha/beta/stable kontejnery jako separátní upstreams)
4. Health monitoring kontejnerů — circuit breaker při opakovaných failurech
5. Pass-through Authorization headeru (JWT validuje až app middleware)
6. Strukturovaný request log + Prometheus metriky per app
7. Vystavuje admin endpointy pro hot-reload / status
Co NEDĚLÁ (záměrně):
- Neukončuje TLS — vm-gateway Nginx už to dělá
- Nevaliduje JWT — to dělá avax-auth middleware uvnitř každého app kontejneru. Gateway musí být rychlá a stateless; navíc validace v gateway by znamenala další round-trip k JWKS endpointu nebo statický PEM v gateway.
- Negeneruje žádné tokeny
- Nedělá body rewriting (jen prefix strip v path)
2. Architektura¶
┌────────────────────────────┐
Klient ─HTTPS─────► │ vm-gateway / Nginx │
(launcher, │ api.avaxis.cz │
web, mobile) │ (TLS termination) │
└──┬─────────────────────┬───┘
│ │
┌─────────┘ └─────────┐
│ /auth/*, /org/*, │ /apps/<slug>/*
│ /catalog/*, /storage/*, │
│ /admin/* │
▼ ▼
┌──────────────────┐ ┌──────────────────────┐
│ core-api │ │ apps-gateway │
│ (FastAPI) │ ──── DB pool ────► │ (FastAPI :8100) │
│ :8000 │ │ - discovery │
└────────┬─────────┘ │ - routing │
│ │ - health monitor │
│ │ - metrics/logs │
▼ └─────────┬────────────┘
┌──────────────────┐ │
│ PostgreSQL │ ◄── read-only ───────────────┘ (apps, app_deployments,
│ + PgBouncer │ (cached, refresh 60s) company_channel_assignments)
└──────────────────┘ │
│
┌──────────────────────┼─────────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ app-legal │ │ app-mzdy │ │ app-xxx │
│ :8101 │ │ :8102 │ │ :81NN │
│ (Docker net) │ │ │ │ │
└──────────────┘ └──────────────┘ └──────────────┘
Síťová topologie (avaxdev):
- Apps-gateway běží jako systemd unit nebo Docker kontejner, port :8100
- App kontejnery běží v Docker bridge networku avax-apps na portech 8101–8199
- Gateway připojena do stejné networku → mluví s nimi přes container hostname (app-legal:8101) NEBO host.docker.internal:81xx
- Nginx → apps-gateway: TCP 8100 na localhost (loopback only)
Síťová topologie (production vm-api): - Stejně, ale apps-gateway běží spolu s containers na vm-api - 2× replika gateway za internal HAProxy / Nginx load balancer pro HA
3. Discovery — DB-driven¶
Apps-gateway čte konfiguraci z apps tabulky core-api DB (read-only). Refresh každých 60 sekund + push-trigger přes Redis pubsub při create_app / update_app / delete_app.
Rozšíření apps modelu (nová migrace)¶
Přidat sloupce:
# backend/app/models/catalog.py — App class
gateway_enabled: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
container_host: Mapped[str] = mapped_column(String(255), default="localhost") # nebo container name
container_port: Mapped[int|None] = mapped_column(Integer) # 8101–8199
container_image: Mapped[str|None] = mapped_column(String(500)) # git.avaxis.cz/avax-apps/legal:1.0.3
health_path: Mapped[str] = mapped_column(String(100), default="/health")
health_status: Mapped[str] = mapped_column(String(20), default="unknown")
# unknown | ok | degraded | down
last_health_check_at: Mapped[datetime|None] = mapped_column(DateTime(timezone=True))
last_health_error: Mapped[str|None] = mapped_column(Text)
gateway_enabled=True = app je registrovaná v gateway. Default False aby existující module-mount apps nezačaly náhle 404-ovat přes gateway.
Per-channel deployments (volitelné v MVP)¶
Pro per-firma routing (alpha/beta/stable) by ideálně každá channel měla vlastní kontejner s vlastním portem. Návrh: nová tabulka app_deployments:
class AppDeployment(Base):
__tablename__ = "app_deployments"
__table_args__ = (UniqueConstraint("app_id", "channel"),)
id: Mapped[uuid.UUID]
app_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("apps.id"))
channel: Mapped[str] = mapped_column(String(20)) # alpha|beta|stable
container_host: Mapped[str]
container_port: Mapped[int]
container_image: Mapped[str]
health_status: Mapped[str]
last_health_check_at: Mapped[datetime|None]
last_health_error: Mapped[str|None]
deployed_at: Mapped[datetime]
deployed_by_user_id: Mapped[uuid.UUID|None]
V MVP gateway podporuje pouze stable (1:1 s apps). Per-channel deploys přidat v Phase 2 — viz Roadmap.
Cache invalidation¶
- Polling refresh — každých 60s gateway re-loaduje
apps WHERE gateway_enabled = TRUE - Push invalidation — core-api publishuje Redis channel
apps.changedpayload{slug, action: "create|update|delete"}, gateway reaguje okamžitě (<1spropagace) - Manual refresh —
POST /admin/refreshendpoint v gateway (jen ze loopbacku)
4. Routing pravidla¶
Path rewriting¶
Request: GET /apps/legal/items/123?foo=bar
────┬──── ─────┬──────
slug sub-path
Upstream: GET http://app-legal:8101/items/123?foo=bar
- Prefix
/apps/<slug>se odstraňuje - Query string a body se předávají beze změny
- Trailing slash zachován:
/apps/legal/→/ - Pokud
<slug>neexistuje v cache →404 {"detail": "Aplikace neexistuje", "slug": "..."} - Pokud
health_status == "down"→503 {"detail": "Aplikace dočasně nedostupná"}
Headery¶
Forwardované 1:1:
- Authorization: Bearer <jwt> — gateway nevalidační
- Accept, Content-Type, Content-Length, User-Agent
- X-Request-ID — pokud klient pošle, jinak gateway vygeneruje UUID4
Přidávané gateway:
- X-Forwarded-For — IP klienta (přes Nginx proxy chain)
- X-Avax-Slug: legal — explicit slug pro app interní log
- X-Avax-Channel: stable — pokud byl použit per-channel deploy
- X-Avax-Gateway-Version: 0.1.0
Odstraňované:
- X-Forwarded-Host (gateway forwarduje vlastní hostname)
- Hop-by-hop headery (Connection, Upgrade, …) — RFC 7230 § 6.1
Body & metody¶
- Všechny HTTP metody:
GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD - Streaming request body (chunked) → streaming proxy do upstreamu (
httpx.AsyncClient.stream) - Streaming response (chunked, SSE, WebSocket) → bidirectional proxy
WebSocket (Phase 2): apps-gateway zatím NE-WS. App který potřebuje WS si může poslat WS přímo přes vm-gateway Nginx (/apps-ws/<slug>/* na separátním backendu) — to je future work.
5. Per-firma channel routing¶
Cíl: firma A je v beta, firma B v stable → každá dostane jiný kontejner stejné app.
Tok requestu¶
1. Klient → /apps/legal/items Authorization: Bearer <jwt>
2. Nginx → apps-gateway (port 8100)
3. apps-gateway:
a) extract slug = "legal"
b) decode JWT (bez validace signature — gateway nezná secret!)
→ claims.company_id
c) lookup channel:
SELECT channel FROM company_channel_assignments
WHERE company_id = $1 AND app_id = $app_legal_id
(cached 5min v gateway, default = "stable")
d) lookup deployment:
SELECT container_host, container_port FROM app_deployments
WHERE app_id = $app_legal_id AND channel = $channel
(default = stable pokud channel deployment neexistuje)
e) forward na vybraný upstream
Bezpečnost: gateway čte company_id z claims bez verifikace signature. To je OK protože:
- Gateway forwarduje request app kontejneru, který ZNOVU validuje JWT s avax-auth middleware
- I kdyby útočník podstrčil forged JWT s jiným company_id, app middleware ho odmítne
- Gateway-level channel lookup je jen routing hint, ne security boundary
Fallback při chybějícím JWT: /health/* paths jsou veřejné, ostatní bez JWT → routing na stable.
MVP cesta (per-channel deploy bez tabulky app_deployments)¶
V MVP gateway čte jen apps.container_host/port (vždy stable). Per-channel routing přidat v Phase 2 po stable funkcionalitě.
6. Health monitoring & circuit breaker¶
Health checks¶
Každých 10 sekund gateway pingne každou registrovanou app:
Stav transitions:
| Trigger | Z → do | Akce |
|---|---|---|
| 200 OK | * → ok |
Requesty propouštět |
| 200 OK s degraded checks | * → degraded |
Requesty propouštět + alert log |
| 503 / timeout / connection error | ok|degraded → degraded |
1× retry za 5s |
| 3 po sobě fail | * → down |
503 na klientské requesty, ping interval 30s |
| 200 OK | down → ok |
Obnovit obsluhu, reset counteru |
health_status + last_health_check_at + last_health_error se zapisují do apps tabulky každých 10s (jednoduché UPDATE).
Circuit breaker pattern¶
Při down gateway nesendí requesty na upstream (jinak by každý request platil 5s timeout cenu). Místo toho rovnou vrátí:
HTTP/1.1 503 Service Unavailable
Content-Type: application/json
Retry-After: 30
{
"detail": "Aplikace 'legal' dočasně nedostupná",
"slug": "legal",
"status": "down",
"last_error": "Connection refused",
"retry_after_s": 30
}
Public status endpoint¶
GET /apps/status (na gateway, bez auth) → seznam všech apps + status. Pro launcher tray widget („avax-legal je momentálně mimo provoz"):
{
"apps": [
{"slug": "legal", "status": "ok", "last_check": "...", "version": "1.0.3"},
{"slug": "mzdy", "status": "degraded", "last_check": "...", "last_error": "DB lag 3s"}
]
}
7. Service tokens (M2M) — OAuth2 client_credentials¶
Implementováno 2026-05-28 (commits da9788d + 9ce3880 v core-api).
Aktualizace tohoto designu z původního service-token návrhu — nahrazeno
standardním OAuth2 client_credentials grant pro snazší interop s vendor
service ekosystémem (CI runners, cron jobs, external SaaS partners).
Use cases¶
Některé background procesy musí volat AI Helper / další apps bez user
kontextu:
- Vendor cron noční reindex legal-precedents corpus
- Batch embedding 1000 dokumentů přes Celery
- External SaaS partner volá /v1/ai/chat/complete jménem koncového klienta
- Per-app backend volá jiný per-app backend (např. mzdy volá legal
pro precedent lookup)
Issuance: POST /admin/m2m-clients (super_admin only)¶
Super_admin vystaví credentials přes core-api admin endpoint:
POST /admin/m2m-clients
Authorization: Bearer <super_admin_jwt>
Content-Type: application/json
{
"owner_vendor_id": "<vendor company UUID>",
"allowed_app_id": "<app UUID>",
"name": "legal-cron-batch",
"description": "...",
"allowed_scopes": ["ai-helper.chat", "ai-helper.rag.query"]
}
→ 201 {
"id": "<UUID>",
"client_id": "avax_m2m_<32 hex>",
"client_secret": "avax_secret_<48 url-safe>", ← PLAINTEXT JEDNOU
...
}
Bezpečnost:
- client_id formát: avax_m2m_<32 hex> (public identifier)
- client_secret formát: avax_secret_<48 url-safe> (~64 chars)
- DB ukládá jen bcrypt hash secretu — plaintext jen v response z create
- Vendor uloží do Bitwarden / CI env vars / docker secret — NIKDY do
conversation logu, gitu, Slack
- Pokud vendor secret ztratí: POST /admin/m2m-clients/{id}/revoke →
vystavit nové
Token issuance: POST /auth/token¶
POST /auth/token
Content-Type: application/json
{
"grant_type": "client_credentials",
"client_id": "avax_m2m_...",
"client_secret": "avax_secret_...",
"scope": "ai-helper.chat ai-helper.rag.query" ← optional subset
}
→ 200 {
"access_token": "<RS256 JWT, kid v headeru>",
"token_type": "Bearer",
"expires_in": 900, ← 15 min default
"scope": "ai-helper.chat ai-helper.rag.query"
}
JWT claims¶
Podepsáno RS256 stejnou platform private key jako user JWT (sdílený
JWKS endpoint /auth/.well-known/jwks.json → per-app AVAX_JWKS_URL).
Avax-auth middleware ho validuje stejně jako user JWT:
{
"sub": "<vendor_uuid>",
"company_id": "<vendor_uuid>", // M2M caller = vendor company
"app_id": "<allowed_app_uuid>", // ACL — apps mohou filtrovat
"vendor_id": "<vendor_uuid>",
"client_id": "avax_m2m_<32 hex>",
"scopes": ["ai-helper.chat", ...],
"is_m2m": true, // distinguishuje od user JWT
"system_role": "m2m", // NE 'user' / 'super_admin'
"tfa_verified": false,
"type": "access",
"exp": <unix ts>
}
Důležitá designová rozhodnutí:
- sub = vendor_uuid (NE vendor:<uuid> prefix — avax-auth validator
očekává parseable UUID v sub)
- company_id = vendor_uuid (vendor JE caller's company sémantika)
- system_role="m2m" umožňuje app code filtrovat (if user.system_role
== "m2m": ... pro audit log / quota separation)
Scope filtering¶
Apps validují že caller má scope na danou operaci:
# V app-ai-helper endpoint handleru:
@router.post("/v1/chat/stream")
async def chat_stream(req: ChatRequest, user: AvaxUser = Depends(...)):
if user.system_role == "m2m":
scopes = user.claims.get("scopes", [])
if "ai-helper.chat" not in scopes:
raise HTTPException(403, "scope ai-helper.chat required")
# ... handler
Předdefinované scopes (per ai-helper.md):
- ai-helper.chat — /v1/chat/*
- ai-helper.embed — /v1/embed*
- ai-helper.rag.query — /v1/rag/query
- ai-helper.rag.write — /v1/rag/corpora + grants
- ai-helper.image — /v1/image/*
- ai-helper.generic_call — /v1/call/{cap_id} (libovolný capability)
V gateway¶
Gateway si žádný service token nedrží — vendor service získá token přes
/auth/token (core-api), pak ho posílá v Authorization: Bearer na
/apps/<slug>/v1/*. Gateway proxuje bez ohledu na auth type (user/M2M).
Validuje až per-app middleware.
Plus: gateway by mohla v budoucnu rate-limitovat per client_id
claim (současné rate-limit per IP) — sekce 10 roadmap.
Admin endpointy¶
GET /admin/m2m-clients — list (filter vendor_id / app_id)
GET /admin/m2m-clients/{id} — detail (bez secret)
POST /admin/m2m-clients/{id}/revoke — soft revoke (existing tokens
expire do 15 min naturally)
DELETE /admin/m2m-clients/{id} — hard delete (POZOR audit trail)
SDK použití (M2M flow)¶
Vendor service v Pythonu:
import httpx, os
from avaxis_sdk import AvaxApp
# Získat M2M JWT
resp = httpx.post("https://api.avaxis.cz/auth/token", json={
"grant_type": "client_credentials",
"client_id": os.environ["AVAX_M2M_CLIENT_ID"],
"client_secret": os.environ["AVAX_M2M_CLIENT_SECRET"],
"scope": "ai-helper.chat ai-helper.rag.query",
})
m2m_token = resp.json()["access_token"]
# Nastav do SDK + použij
app = AvaxApp(slug="moje-cron")
app.access_token = m2m_token
text = await app.ai.chat_complete(prompt="...")
Per-app backend M2M flow podobně — httpx.post na core-api, výsledný
token použít v dalších volání.
8. Failure modes¶
| Selhání | Co gateway dělá | Co klient vidí |
|---|---|---|
| Slug neexistuje | 404 | {"detail": "Aplikace neexistuje", "slug": "..."} |
| Slug existuje, health = down | 503 (circuit broken) | {"detail": "Aplikace dočasně nedostupná", "retry_after_s": 30} |
| Upstream timeout (10s) | 504 Gateway Timeout | {"detail": "App neodpovídá", "timeout_s": 10} |
| Upstream 5xx | proxy 5xx beze změny | upstream tělo + status |
| Upstream připojení selhalo (connection refused) | 502 Bad Gateway | {"detail": "App nedosažitelná"} |
| DB pro discovery nedostupná | Použij in-memory cache (last known), log error | klient transparentně dostane response |
| Redis pubsub down | Žádný error — fallback na polling refresh | — |
Request body při retry: gateway nereetri request body při upstream selhání (idempotence není garantována; PUT/POST mohou mít side effects). Klient může retry sám.
9. Observability¶
Strukturovaný log (JSON do stdout)¶
{
"ts": "2026-05-14T17:30:00Z",
"level": "INFO",
"logger": "apps_gateway",
"request_id": "uuid4",
"method": "POST",
"path": "/apps/legal/items",
"slug": "legal",
"channel": "stable",
"upstream": "app-legal:8101",
"status": 200,
"duration_ms": 42,
"user_id": "<uuid>", // z JWT claims, ne validovaný
"company_id": "<uuid>",
"bytes_in": 1234,
"bytes_out": 5678
}
Agregace přes vm-monitor (Loki) — již existující stack.
Prometheus metriky¶
GET /metrics na gateway (port 8100, no auth, ale binded na loopback):
# Counters
apps_gateway_requests_total{slug, channel, method, status}
apps_gateway_upstream_errors_total{slug, channel, error_type}
# Histograms
apps_gateway_request_duration_seconds{slug, channel} # response time
apps_gateway_upstream_duration_seconds{slug, channel} # bez gateway overheadu
# Gauges
apps_gateway_app_health{slug, channel} # 0=down, 1=degraded, 2=ok
apps_gateway_active_requests{slug}
apps_gateway_cached_apps_count
vm-monitor (Prometheus) scrape každých 15s.
Tracing (Phase 3+)¶
OpenTelemetry → Tempo. V MVP nesoučástí — přidat až budou apps multi-hop (app → core-api → DB).
10. Rate limiting (Phase 2)¶
V MVP bez rate limitingu. V Phase 2:
- Per company × per app:
100 req/mindefault, override vapps.rate_limit_per_company_per_min - Per user × per app:
30 req/mindefault - Globální per app:
1000 req/minper kontejner replika (DoS protection)
Implementace: in-memory token bucket per (company_id, slug) klíč. Pro multi-replika gateway: Redis-based bucket (redis-cell extenze nebo Lua skript).
Při překročení: 429 Too Many Requests + Retry-After: <s>.
11. Deploy¶
Avaxdev (dev)¶
Systemd unit avax-apps-gateway.service:
[Unit]
Description=AVAX apps-gateway (FastAPI reverse proxy)
After=network.target postgresql.service redis-server.service avax-backend-bare.service
Wants=avax-backend-bare.service
[Service]
Type=exec
User=avax
WorkingDirectory=/home/avax/avax-platform/apps-gateway
Environment="AVAX_GATEWAY_DB_URL=postgresql+asyncpg://..."
Environment="AVAX_GATEWAY_REDIS_URL=redis://localhost:6379/3"
Environment="AVAX_GATEWAY_PORT=8100"
Environment="AVAX_GATEWAY_LOG_LEVEL=INFO"
ExecStart=/home/avax/avax-platform/.venv/bin/uvicorn apps_gateway.main:app \
--host 127.0.0.1 --port 8100 \
--proxy-headers --forwarded-allow-ips=127.0.0.1
Restart=on-failure
RestartSec=3s
[Install]
WantedBy=multi-user.target
Code lives in apps-gateway/ v hlavním repu (peer s backend/).
Production vm-api (Phase 5)¶
Docker compose service v vm-api/docker-compose.yml:
services:
apps-gateway:
image: git.avaxis.cz/avaxis/apps-gateway:0.1.0
networks: [avax-apps]
environment:
AVAX_GATEWAY_DB_URL: ${DB_URL}
AVAX_GATEWAY_REDIS_URL: redis://redis:6379/3
ports:
- "127.0.0.1:8100:8100"
deploy:
replicas: 2
Nginx config¶
# vm-gateway nginx — /etc/nginx/sites-available/api.avaxis.cz
location /apps/ {
proxy_pass http://127.0.0.1:8100;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 60s;
proxy_buffering off; # streaming
}
12. Konfigurace (env vars)¶
| Variable | Default | Účel |
|---|---|---|
AVAX_GATEWAY_PORT |
8100 |
Listen port |
AVAX_GATEWAY_DB_URL |
(none) | PG connection pro discovery (read-only user doporučen) |
AVAX_GATEWAY_REDIS_URL |
redis://localhost:6379/3 |
Pubsub channel apps.changed |
AVAX_GATEWAY_REFRESH_S |
60 |
Polling interval pro DB refresh |
AVAX_GATEWAY_HEALTH_INTERVAL_S |
10 |
Health check tick |
AVAX_GATEWAY_HEALTH_TIMEOUT_S |
5 |
Health check timeout |
AVAX_GATEWAY_UPSTREAM_TIMEOUT_S |
60 |
Upstream request timeout |
AVAX_GATEWAY_LOG_LEVEL |
INFO |
uvicorn log level |
AVAX_GATEWAY_METRICS_ENABLED |
true |
Vystavit /metrics |
13. Bezpečnost¶
- Network isolation: gateway binded jen na
127.0.0.1:8100. Veřejně přístupná POUZE přes Nginx s TLS. - JWT: gateway nevalidační (perf reason). App middleware validuje. Forged JWT s vyšším system_role nepomůže — app validuje signature.
- DB user pro discovery: read-only role
avax_gatewaysSELECTjen naapps,app_deployments,company_channel_assignments. - Service token secrets: nikdy v gateway logu. App secret leak → admin musí rotovat přes admin UI.
- Audit log: každý request s
user_id(z claims) do strukturovaného logu. Slug ssystem_role=serviceoznačen jako M2M call. - DoS: rate limiting per company (Phase 2), upstream connection pool s max-conn limitem per app.
14. Otevřené otázky¶
- WebSockets — gateway WS proxy v MVP, nebo přes Nginx s vlastním backendem? Začínáme bez WS v gateway, doladit s prvním WS-using app.
- Streaming uploads (multipart) — limit velikosti? Default
100 MB(Nginxclient_max_body_size) + gateway nestreamuje do disku, jen pipe. - Per-channel deployments — DB schema
app_deploymentszavést v MVP, nebo až v Phase 2? Pro MVP stačí 1:1 vappstabulce; v Phase 2 přidat tabulku. - JWT decode bez validace v gateway — extrahovat
company_idz claims. Alternativně předat plain a app vrátícompany_idvX-Avax-Company-IDresponse headeru, gateway si to nakešuje pro analytics. (Preferujeme prvotní variantu — jednoduší.) - Service token scope — JWT bez
company_idznamená "globální". Má app endpoint požadovatcompany_idjako URL parameter pro service-role calls, nebo z headeruX-Avax-Target-Company? - CI deploy app — kdo registruje
container_port? Vendor vapp.ymlrepa? CI auto-allocate z range 8101–8199? - Cross-app komunikace — povolit
app-legalvolatapp-mzdypřes gateway (http://apps-gateway:8100/apps/mzdy/...)? Bezpečnostně to není problém (každá app validuje JWT/service token), ale discouraged — preferovat přes core-api. - Graceful shutdown — při SIGTERM dokončit in-flight requests (uvicorn handles, ale s explicit timeout 30s).
- Multi-replika sync — pokud běží 2 instance gateway (HA), oba dělají health check (zbytečné × 2). Lze koordinovat přes Redis lock (jen 1 dělá písemnou kontrolu, ostatní jen čtou výsledek z DB), ale jednoduší: oba ping, last-write-wins na DB.
15. Roadmap¶
| Fáze | Co | Cílový termín |
|---|---|---|
| Phase 1 — Spec | Tento dokument + review | 2026-05-14 ✓ |
| Phase 2a — Skeleton | apps-gateway/ adresář, FastAPI app, env config, systemd unit |
TBD |
| Phase 2b — DB rozšíření | Migrace 020: apps.gateway_enabled/container_host/port/... + read-only role avax_gateway |
TBD |
| Phase 2c — Discovery | DB read + Redis pubsub apps.changed |
TBD |
| Phase 2d — Routing | Prefix strip, header passthrough, httpx upstream | TBD |
| Phase 2e — Health | /health polling + circuit breaker + apps.health_status writeback |
TBD |
| Phase 2f — Observability | JSON logy + Prometheus /metrics |
TBD |
| Phase 3 — Pilot avax-legal | Skutečná migrace avax_legal modulu do kontejneru za gateway |
TBD |
| Phase 4 — Per-channel | Tabulka app_deployments, channel routing z company_channel_assignments |
TBD |
| Phase 5 — Service tokens | POST /auth/service-token, avax-auth.require_service_role() |
TBD |
| Phase 6 — Rate limit | Per company × per app token bucket | TBD |
| Phase 7 — Production HA | 2× replika + Nginx LB na vm-api | TBD |
16. Akceptační kritéria — Phase 2 MVP¶
-
apps-gatewaystartuje na:8100(uvicornz systemd unit) - Při startu načte
apps WHERE gateway_enabled=TRUEz DB -
GET /apps/<slug>/healthproxy-uje nahttp://<container_host>:<container_port>/health - Prefix strip funguje (
/apps/legal/items→ upstream/items) - Pass-through
Authorizationheaderu - Health monitor každých 10s,
apps.health_statusaktualizovaný - Circuit breaker: 3 fail →
down→ klient dostane503bez čekání na timeout -
GET /apps/statusveřejný endpoint vrátí JSON se stavem všech apps -
GET /metricsexportuje Prometheus formátu - Strukturované JSON logy do stdout
- Smoke test: nasimulovat 1 app (Python
http.server) registrovaný v DB,curl /apps/test/foodorazí, restart app →down→ opětup
17. Související¶
- Hlavní arch spec:
per-app-container.md - Backend deploy:
backend-deploy.md - Infra:
server-infrastructure.md - App distribution:
app-distribution.md - Vendor onboarding:
vendor-onboarding.md - Pilot:
avax-legal.md