docs: Diplodoc setup + полное руководство интеграции по API

Новая документация-as-code на платформе Diplodoc (Yandex open-source) с
YFM (Yandex Flavored Markdown). Build → static HTML, deployable куда угодно.

Что добавлено:
- package.json + .yfm + toc.yaml — Diplodoc setup, навигация для sidebar
- index.md — главная страница с tabs для разных audiences
- README.md — инструкция для contributors

Раздел integration/api/ — 11 страниц про интеграцию consumers:
- index — обзор + глоссарий
- auth — Keycloak service accounts, scope mapping
- read-endpoints — GET /dictionaries, list/by-key, spatial filters
- events — Kafka outbox топики, partition keys, idempotency, DLQ
- webhooks — HMAC, SSRF guard, retry policy, test ping endpoint
- bitemporal — validFrom/validTo + history, future-dated entries, bulk close/export
- x-references — FK syntax, validation errors, scope hiding, orphan scanner, v2 cascade preview
- caching — TTL / push / snapshot strategies, hybrid pattern
- errors — full error code reference (validation/auth/conflict/server), retry strategy
- best-practices — 33 numbered rules для consumers
- bundle-cuod — все 40 справочников с FK графом и сценариями

Обновлены:
- ops/README.md — link на новую integration/api/, убран inline <br>
- ops/slo.md — broken cross-repo links заменены на text references

Build:
  cd docs && pnpm install && pnpm build
  → ./_dist (20 HTML files, 7.1M)
  pnpm serve  # → http://localhost:8088

CI deploy job для GitLab Pages — в roadmap (см. docs/README.md).

Существующий tech/api-integration.md оставлен в toc как legacy
(содержимое мигрировано + расширено в integration/api/).
This commit is contained in:
Zimin A.N.
2026-05-07 23:19:02 +03:00
parent 0c79821f8e
commit d5268b9395
20 changed files with 3450 additions and 5 deletions
+216
View File
@@ -0,0 +1,216 @@
# Webhooks
Альтернатива Kafka events для consumer'ов которые не хотят / не могут
поддерживать Kafka client. Ordinis отправляет HTTP POST на зарегистрированный
URL при изменениях справочников.
{% note info %}
Если у тебя есть Kafka access — используй [события](events.md). Webhooks
проще в развёртывании, но менее надёжны (HTTP retry vs Kafka durable log)
и медленнее (HTTP overhead vs Kafka batch).
{% endnote %}
## Регистрация подписки
```bash
curl -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
https://ordinis.k8s.264.nstart.cloud/api/v1/admin/webhooks/subscriptions \
-d '{
"name": "altum-cache-invalidation",
"url": "https://altum-backend.altum.local/webhooks/ordinis",
"secret": "<HMAC_SECRET_GENERATED_BY_YOU>",
"scopes": ["PUBLIC", "INTERNAL"],
"dictionaries": ["spacecraft", "satellite_type", "instrument"],
"actions": ["created", "updated", "closed"],
"active": true
}'
```
### Поля
| Поле | Тип | Назначение |
|---|---|---|
| `name` | string | Человекочитаемое имя для admin UI. |
| `url` | string | HTTPS endpoint. HTTP блокируется SSRF guard'ом. |
| `secret` | string | HMAC-SHA256 секрет для подписи payload (см. ниже). |
| `scopes` | array | Фильтр по scope (subset из доступных consumer'у). |
| `dictionaries` | array | Фильтр по dictionary names. `null`/`[]` = все доступные. |
| `actions` | array | `created` / `updated` / `closed`. `null` = все. |
| `active` | bool | Можно временно отключить без удаления. |
## SSRF guard
Webhook URLs валидируются перед отправкой:
- **Запрещено:** private CIDR (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16),
localhost, link-local (169.254/16), metadata IPs.
- **Запрещено:** non-HTTPS scheme. Только `https://`.
- **Запрещено:** URL > 2048 chars.
Метрика `nsi_webhook_ssrf_rejected_total` increments при попытках. См.
prometheus rule `OrdinisWebhookSsrfRejected`.
## Payload
POST на зарегистрированный URL с body:
```json
{
"deliveryId": "wd-abc123-xyz789",
"subscriptionName": "altum-cache-invalidation",
"occurredAt": "2026-05-07T15:23:45.123+03:00",
"events": [
{
"schemaVersion": "1.0.0",
"eventType": "dictionary.changed",
"dictionary": "spacecraft",
"businessKey": "RESURS-P-3",
"action": "updated",
"version": 3,
"validFrom": "2026-05-07T00:00:00+03:00",
"data": { ... }
}
]
}
```
{% note tip %}
Payload может содержать **несколько events** (`events` array) если они
произошли в одной transaction (bulk close, bundle import). Не предполагай
single-event payload.
{% endnote %}
## HMAC подпись
Каждый POST имеет header:
```
X-Ordinis-Signature: sha256=<HEX>
X-Ordinis-Delivery: wd-abc123-xyz789
X-Ordinis-Subscription: altum-cache-invalidation
```
Где `<HEX>` = HMAC-SHA256 от raw body bytes с `secret` который ты задал
при регистрации.
### Verify (Python)
```python
import hmac, hashlib
def verify(body: bytes, signature_header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(), body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature_header)
```
### Verify (Node.js)
```javascript
import crypto from 'crypto'
function verify(body, signatureHeader, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(body)
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signatureHeader)
)
}
```
{% note warning %}
Verify подпись **до** парсинга JSON. Если invalid — `403 Forbidden`,
не трогай payload. Без verify ты уязвим к подделке events от любого, кто
знает твой URL.
{% endnote %}
## Response contract
Consumer должен ответить `2xx` в течение 10 секунд:
| Status | Семантика |
|---|---|
| `200` / `201` / `202` / `204` | Принято. Ordinis помечает delivered. |
| `4xx` | Permanent failure (твой bug). Ordinis НЕ retry. |
| `5xx` / timeout | Transient. Ordinis retry с exponential backoff. |
## Retry policy
| Попытка | Интервал |
|---|---|
| 1 | сразу |
| 2 | через 30s |
| 3 | через 2 мин |
| 4 | через 10 мин |
| 5 | через 1ч |
| 6-10 | каждые 6ч |
| 1000+ | DLQ → ручной разбор |
После 1000 неудач delivery попадает в DLQ (`/api/v1/admin/webhooks/deliveries/dlq`).
Alert: `OrdinisWebhookDLQNonEmpty`.
## Test ping
Endpoint для проверки доступности URL **до** регистрации:
```bash
curl -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
https://ordinis.k8s.264.nstart.cloud/api/v1/admin/webhooks/test-ping \
-d '{
"url": "https://altum-backend.altum.local/webhooks/ordinis",
"secret": "<TEST_SECRET>"
}'
```
Response:
```json
{
"success": true,
"statusCode": 200,
"responseBody": "OK",
"elapsedMs": 142
}
```
Либо details про failure (DNS issue, timeout, SSRF reject и т.д.).
## Best practices
1. **Idempotency** — webhook может прийти **дважды** (network retry, partial
failure). Используй `deliveryId` как dedup key.
2. **Async обработка** — отвечай `202 Accepted` сразу, обрабатывай в
background. 10s timeout жёсткий — если БД медленная, лучше принять и
сложить в очередь.
3. **HMAC verify первым** — до парсинга JSON.
4. **Metrics** — track received / processed / errors для своего side.
Сравнивай с Ordinis metrics при расследовании.
5. **Whitelist Ordinis IP** на firewall если строгие правила. IP cluster.142
= `192.168.100.142`, но в production будет ingress IP. Спросить у Ordinis
team при registration.
## Alerting (Ordinis side)
| Alert | Trigger |
|---|---|
| `OrdinisWebhookHighFailureRate` | failure rate > 50% за 5 мин |
| `OrdinisWebhookDLQNonEmpty` | events в DLQ > 0 |
| `OrdinisWebhookDispatcherIdle` | 0 deliveries при > 0 active subscriptions |
| `OrdinisWebhookSsrfRejected` | SSRF guard сработал > 0 раз за 15 мин |
## Дальше
- [События (Kafka)](events.md) — primary delivery channel
- [Caching](caching.md) — webhook invalidation pattern