docs: split integrator guide (Diplodoc) vs internal docs + add build-docs CI

Чёткое разделение audiences:
- docs/ — consumer-facing only, Diplodoc-built. Public contract для
  интеграторов (Альтум, BI, third-party).
- docs-internal/ — operator runbooks, status snapshots, legacy tech pages.
  НЕ в Diplodoc build, НЕ публикуются.

Изменения:
- Move docs/{ops,status,tech} → docs-internal/{ops,status,tech}
- docs/toc.yaml: убраны секции Operations, Архитектура (legacy), Status
- docs/index.md: убран tab "оператор / on-call", focus на integrators
- docs/README.md: rewritten — scope только integrator guide, editorial
  guidelines (no leak internal architecture, stable API contract)
- .yfm: убран явный ignore status/* (теперь не нужно — папка переехала)
- docs-internal/README.md: index для внутренней документации с rationale
  разделения

Scrubbing implementation details из Diplodoc страниц:
- events.md: убраны Strimzi/cluster.142 references, OutboxPoller детали,
  internal alert names (OrdinisOutboxLagHigh, OrdinisOutboxDLQNonEmpty),
  metric names (nsi_outbox_*). Заменены на consumer-observable contract.
- webhooks.md: убраны metric names (nsi_webhook_ssrf_rejected_total),
  alert table (OrdinisWebhookHighFailureRate и т.д.). Retry policy
  переписана на user-side perspective.
- errors.md: убран alert name OrdinisReadApiErrorBudgetBurnFast, Tempo
  endpoint URL, internal connection details.
- x-references.md: OrphanReferenceScanner → general "Ordinis периодически
  детектирует". Убраны metric/alert names. Cascade roadmap без link на
  internal design doc.
- auth.md: убран file path ScopeContext.java.
- bundle-cuod.md: убран Maven module path.
- caching.md: cascade design link заменён на text reference.

CI:
- .gitlab-ci.yml: новый job build-docs (stage: build) — Node 20 alpine,
  pnpm install --frozen-lockfile + pnpm build, artifact docs/_dist на
  неделю. Triggers: auto на docs/**, либо manual web. Готов к follow-up
  pages-deploy job когда host решён.
- Новый pattern .docs-changes для rules.

Build verified clean: 13 HTML pages, 6.9M, no broken links/refs.
This commit is contained in:
Zimin A.N.
2026-05-07 23:27:03 +03:00
parent d5268b9395
commit bcfb07f547
23 changed files with 232 additions and 215 deletions
+38
View File
@@ -54,6 +54,12 @@ default:
.frontend-changes: &frontend-changes .frontend-changes: &frontend-changes
- "ordinis-admin-ui/**/*" - "ordinis-admin-ui/**/*"
# Documentation changes — Diplodoc-built integrator guide.
# docs-internal/ исключено явно: оно НЕ build'ится Diplodoc'ом и не должно
# триггерить docs job.
.docs-changes: &docs-changes
- "docs/**/*"
# ─── TEST ────────────────────────────────────── # ─── TEST ──────────────────────────────────────
maven-test: maven-test:
stage: test stage: test
@@ -112,6 +118,38 @@ maven-package:
- ordinis-migrations/src/main/resources/db/changelog/** - ordinis-migrations/src/main/resources/db/changelog/**
expire_in: 1 day expire_in: 1 day
# ─── BUILD: Integrator Guide (Diplodoc) ────────
# Build static HTML documentation для consumers через @diplodoc/cli (yfm).
# Source — docs/ (Markdown + toc.yaml). Output — docs/_dist/ HTML.
#
# Triggers:
# - auto при изменении docs/**
# - manual через web UI (для force-rebuild без code changes)
#
# Artifact `docs/_dist/` сохраняется неделю — pages-deploy job (TBD,
# когда host решён — GitLab Pages либо k8s ingress) подхватит из него.
build-docs:
stage: build
image: repo.nstart.cloud/library/node:20-alpine
before_script:
- corepack enable
- corepack prepare pnpm@9.0.0 --activate
script:
- cd docs
- pnpm install --frozen-lockfile
- pnpm build
- echo "Built $(find _dist -name '*.html' | wc -l) HTML pages, total size:"
- du -sh _dist
artifacts:
name: "ordinis-docs-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA"
paths:
- docs/_dist
expire_in: 1 week
rules:
- changes: *docs-changes
- if: '$CI_PIPELINE_SOURCE == "web"'
when: manual
# ─── PUBLISH (Docker images) ─────────────────── # ─── PUBLISH (Docker images) ───────────────────
.docker_base: .docker_base:
stage: publish stage: publish
+57
View File
@@ -0,0 +1,57 @@
# Ordinis — internal documentation
Технические детали реализации, архитектура, operator runbooks и project state
snapshots. **НЕ deployment'ится** через Diplodoc — это внутренние артефакты для
команды Ordinis (не для consumers).
Consumer-facing документация находится в [`docs/`](../docs/) и build'ится через
Diplodoc → static HTML.
## Что внутри
```
docs-internal/
├── ops/ # Operator runbook (on-call, incidents, recovery)
│ ├── README.md
│ ├── slo.md
│ ├── outbox-dlq.md
│ ├── db-migrations.md
│ ├── vault-unseal.md
│ └── kafka-restart.md
├── status/ # Project state snapshots (по дням сессий)
│ └── *-state.md
└── tech/ # Архитектурные deep-dives, legacy документы
└── api-integration.md # legacy (мигрирован в docs/integration/api/)
```
## Когда что использовать
| Situation | Read |
|---|---|
| Я — consumer интегрирующий свой backend | [docs/integration/api/](../docs/integration/api/index.md) (Diplodoc) |
| Я — on-call инженер с alert | [ops/README.md](ops/README.md) |
| Я — Ordinis dev планирующий feature | [status/](status/) latest snapshot + design docs в `~/.gstack/projects/claude/` |
| Я — новый разработчик в команде | Сначала consumer docs, потом ops/, потом status/ для context |
## Архитектурные artifacts (вне репозитория)
Design docs / план-плансы хранятся в `~/.gstack/projects/claude/`:
- `zimin-main-design-production-prep-20260507-123032.md` — prod stand-up plan
- `zimin-main-design-approval-workflow-v2-20260507-121632.md` — approval flow v2 (DEFERRED)
- `zimin-main-design-dict-relationships-v2-20260507-230237.md` — lineage + cascade rules
- `checkpoints/*.md` — session state checkpoints для context-restore
## Rationale разделения
Diplodoc документация — **public contract** для интеграторов:
- Может быть опубликована на public Pages
- Не должна leak'ать internal architecture (security)
- Должна быть стабильной (consumer'ы строят на ней свои интеграции)
`docs-internal/`**operational state**:
- Runbooks меняются часто (incidents, lessons learned)
- Status snapshots — historical record для команды
- Tech deep-dives — для разработчиков, не consumers
Это разделение делает Diplodoc более фокусированным и менее шумным
для consumers.
-1
View File
@@ -8,5 +8,4 @@ strict: false
ignore: ignore:
- node_modules - node_modules
- _dist - _dist
- status/*-state.md
addSystemMeta: true addSystemMeta: true
+54 -74
View File
@@ -1,42 +1,44 @@
# Ordinis docs (Diplodoc / YFM) # Ordinis Integrator Guide (Diplodoc / YFM)
Документация Ordinis НСИ ЦУОД ОДХ построена через **Diplodoc** (open-source Consumer-facing документация Ordinis НСИ ЦУОД. Build через **Diplodoc**
documentation platform от Yandex). Markdown source — YFM (Yandex Flavored (open-source documentation platform от Yandex), source — YFM (Yandex
Markdown) с расширениями для tabs, notes, includes, mermaid диаграмм. Flavored Markdown).
**Scope:** только то, что нужно интеграторам (Альтум team, BI, third-party
consumers). Operator runbooks, архитектурные deep-dives и project state —
во внутренней документации в [`../docs-internal/`](../docs-internal/),
не в Diplodoc build.
## Структура ## Структура
``` ```
docs/ docs/
├── package.json # @diplodoc/cli + scripts ├── package.json # @diplodoc/cli + build scripts
├── .yfm # Diplodoc config (langs, plugins) ├── .yfm # Diplodoc config
├── toc.yaml # Содержание / навигация (рендерится в sidebar) ├── toc.yaml # Содержание / sidebar
├── index.md # Главная страница ├── index.md # Главная (integrator-focused tabs)
── integration/ # Интеграция по API ── integration/
├── api/ # Главы для consumers ├── api/ # 11 глав про API integration
│ ├── index.md # Обзор │ ├── index.md
│ ├── auth.md # Keycloak / JWT │ ├── auth.md
│ ├── read-endpoints.md │ ├── read-endpoints.md
│ ├── events.md # Kafka outbox │ ├── events.md
│ ├── webhooks.md │ ├── webhooks.md
│ ├── bitemporal.md │ ├── bitemporal.md
│ ├── x-references.md │ ├── x-references.md
│ ├── caching.md │ ├── caching.md
│ ├── errors.md │ ├── errors.md
│ ├── best-practices.md │ ├── best-practices.md
│ └── bundle-cuod.md │ └── bundle-cuod.md
└── altum-imaging-order.md # Прикладной сценарий └── altum-imaging-order.md
├── ops/ # Operator runbook ```
│ ├── README.md
│ ├── slo.md Внутренние материалы (НЕ в Diplodoc build):
│ ├── outbox-dlq.md ```
│ ├── db-migrations.md docs-internal/
│ ├── vault-unseal.md ├── ops/ # Operator runbook (on-call)
│ └── kafka-restart.md ├── status/ # Project state snapshots
── status/ # State snapshots (исключено из build) ── tech/ # Legacy technical pages
│ └── *-state.md
└── tech/ # Legacy технические страницы
└── api-integration.md
``` ```
## Сборка ## Сборка
@@ -48,62 +50,40 @@ cd docs
pnpm install pnpm install
pnpm build # → ./_dist (HTML) pnpm build # → ./_dist (HTML)
pnpm serve # → http://localhost:8088 pnpm serve # → http://localhost:8088
# либо одной командой: pnpm dev # build + serve одной командой
pnpm dev
``` ```
Build output (`_dist/`) — статический HTML, можно деплоить в: Build output (`_dist/`) — статический HTML, deployable в:
- GitLab Pages - GitLab Pages
- S3 + CloudFront - S3 / object storage
- nginx/ingress на cluster.142 - nginx/ingress на k8s cluster
## Автор содержимого ## Editorial guidelines
| Раздел | Owner | Этот set документации — **public contract** для интеграторов:
|---|---|
| `/` (index, integration/api/*) | zimin.an@nstart.space |
| `ops/*` | on-call rotation team |
| `integration/altum-imaging-order.md` | Альтум team contact |
## Что не committable 1. **Не leak'аем internal architecture** — никаких internal alert names,
service paths, internal infrastructure URLs.
2. **API contract стабилен** — изменения требуют semver bump + announcement.
3. **Consumer-side examples** для каждой концепции (Python, Node, либо Spring).
4. **Версионирование** через bundle / API major.
- `_dist/` — build output (gitignore'нут) Для operator-side / dev-side материалов — `docs-internal/` либо design docs
- `node_modules/` — dependencies (gitignore'нут) в `~/.gstack/projects/claude/`.
## Deployment
В roadmap — GitLab CI job `build-docs`:
```yaml
build-docs:
stage: build
image: node:18
script:
- cd docs
- pnpm install --frozen-lockfile
- pnpm build
artifacts:
paths: [docs/_dist]
expire_in: 1 week
rules:
- changes: ["docs/**/*"]
```
Затем deploy в GitLab Pages либо как отдельный k8s deployment с nginx.
## YFM extensions используются ## YFM extensions используются
- `{% list tabs %}` — табы для switching между audiences - `{% list tabs %}` — табы для switching между audiences
- `{% note info|warning|tip %}` — выделенные блоки - `{% note info|warning|tip %}` — выделенные блоки
- `mermaid` code blocks — диаграммы (graph LR, gantt) - `mermaid` code blocks — диаграммы (graph LR, gantt)
- Cross-links в `toc.yaml` — sidebar navigation - `toc.yaml` cross-links — sidebar navigation
- `varsPreset: external` — для shared includes (TBD when нужно)
## Roadmap ## Roadmap
- [x] Базовая структура integration guide (11 страниц)
- [ ] CI job `build-docs` в `.gitlab-ci.yml` - [ ] CI job `build-docs` в `.gitlab-ci.yml`
- [ ] GitLab Pages deployment - [ ] GitLab Pages / k8s ingress deployment
- [ ] Search через Diplodoc search plugin - [ ] Search plugin
- [ ] EN translation (i18n via `langs:` в `.yfm`) - [ ] EN translation (через `langs:` в `.yfm`)
- [ ] Includes для shared snippets (auth examples, error tables) - [ ] Includes для shared snippets (auth examples, error tables)
- [ ] Versioning — separate doc trees per Ordinis major version - [ ] Versioning — separate doc trees per API major version
+11 -14
View File
@@ -1,6 +1,8 @@
# Ordinis НСИ ЦУОД ОДХ # Ordinis НСИ ЦУОД ОДХ — Integrator Guide
Документация для разработчиков, интеграторов и операторов. Документация для команд, интегрирующих свои сервисы с Ordinis НСИ ЦУОД.
Сюда не входят operator runbooks и архитектурные deep-dives — те материалы
во внутренней документации проекта.
**Anchor v1 — production LIVE на cluster.142** (с 2026-05-07). **Anchor v1 — production LIVE на cluster.142** (с 2026-05-07).
Альтум consumers подключены, blue/green cutover завершён. Альтум consumers подключены, blue/green cutover завершён.
@@ -26,14 +28,10 @@
- [Bundle cuod](integration/api/bundle-cuod.md) — список 40 справочников ЦУОД - [Bundle cuod](integration/api/bundle-cuod.md) — список 40 справочников ЦУОД
- [Best practices](integration/api/best-practices.md) — частые ошибки - [Best practices](integration/api/best-practices.md) — частые ошибки
- Я — оператор / on-call - Я обрабатываю ошибки
- [Runbook indexed by symptom](ops/README.md) - [Ошибки и rate limits](integration/api/errors.md) — error codes, retry strategy
- [SLO + alert thresholds](ops/slo.md) - [Лучшие практики](integration/api/best-practices.md) — robust integration
- [Outbox DLQ](ops/outbox-dlq.md)
- [Database migrations](ops/db-migrations.md)
- [Vault unseal](ops/vault-unseal.md)
- [Kafka restart](ops/kafka-restart.md)
{% endlist %} {% endlist %}
@@ -41,15 +39,14 @@
| Среда | URL | Auth | Назначение | | Среда | URL | Auth | Назначение |
|---|---|---|---| |---|---|---|---|
| **prod** | `https://ordinis.k8s.264.nstart.cloud/` | JWT обязателен | Альтум real users | | **prod** | `https://ordinis.k8s.264.nstart.cloud/` | JWT обязателен | Real users |
| staging | `https://ordinis.k8s.265.nstart.cloud/` | optional (legacy mode) | Dev environment | | staging | `https://ordinis.k8s.265.nstart.cloud/` | optional (legacy mode) | Dev environment |
| local | `http://localhost:8080` | off | docker-compose dev | | local | `http://localhost:8080` | off | docker-compose dev |
## Поддержка ## Поддержка
- Issues / bug reports: `git.nstart.cloud:2-6/2-6-4/terravault/ordinis` - Запросы новых ролей / scope в Keycloak: `zimin.an@nstart.space`
- Эскалация: `zimin.an@nstart.space` - Bug reports / feature requests: через issue tracker (запросить URL у Ordinis team)
- Запросы новых ролей / scope в Keycloak: тот же email.
## Версионирование ## Версионирование
@@ -59,4 +56,4 @@ API стабилизирован под v1 — breaking changes только ч
Bundle справочников ЦУОД versioned отдельно (`cuod-bundle:1.2.1`). Изменение Bundle справочников ЦУОД versioned отдельно (`cuod-bundle:1.2.1`). Изменение
schemas — minor bump, добавление словарей — minor bump, удаление — major bump schemas — minor bump, добавление словарей — minor bump, удаление — major bump
(никогда без уведомления consumers через `nsi.dictionary.changed` event). (никогда без уведомления consumers через `record.closed` events).
+1 -1
View File
@@ -69,7 +69,7 @@ Ordinis использует трёхуровневый доступ к слов
Альтернативный синтаксис ролей (для compat с другими сервисами в realm): Альтернативный синтаксис ролей (для compat с другими сервисами в realm):
суффикс `-PUBLIC|INTERNAL|RESTRICTED` (например `altum-PUBLIC`, суффикс `-PUBLIC|INTERNAL|RESTRICTED` (например `altum-PUBLIC`,
`geoportal-INTERNAL`). Маппинг определяется в `ordinis-auth/.../ScopeContext.java`. `geoportal-INTERNAL`). Маппинг встроен в auth layer Ordinis.
{% note info %} {% note info %}
+3 -2
View File
@@ -77,8 +77,9 @@ graph LR
I --supportedLevelCodes--> PL I --supportedLevelCodes--> PL
``` ```
(Это subset, для full графа см. отдельные schemas в коде: (Это subset. Полный граф можно получить через `GET /dictionaries` +
`ordinis-cuod-bundle/src/main/resources/cuod/schemas/`.) inspection schemas через `GET /dictionaries/{name}` — каждый возвращает
JSON Schema с x-references маркерами.)
## Типичные сценарии ## Типичные сценарии
+6 -4
View File
@@ -24,7 +24,10 @@ useQuery({
}) })
``` ```
### Server-side кэш (Caffeine + Spring) ### Server-side кэш (на consumer side)
Любая стандартная in-memory cache library с TTL — Caffeine, Guava,
Redis с EXPIRE и т.д. Пример (Spring + Caffeine):
```java ```java
@Cacheable(value = "ordinis-records", key = "#dictionary + ':' + #lang") @Cacheable(value = "ordinis-records", key = "#dictionary + ':' + #lang")
@@ -170,14 +173,13 @@ referencing records. Consumer должен слушать events и invalidate
def on_event(event): def on_event(event):
cache.delete(f"ordinis:{event['dictionary']}:byKey:{event['businessKey']}") cache.delete(f"ordinis:{event['dictionary']}:byKey:{event['businessKey']}")
# NEW v2: cascade source — заодно invalidate dependents # NEW v2: cascade source — заодно invalidate источник
if event.get('cascadeSource'): if event.get('cascadeSource'):
# Источник тоже нужно invalidate (мы видим только закрытие dependent)
src = event['cascadeSource'] src = event['cascadeSource']
cache.delete(f"ordinis:{src['dictionary']}:byKey:{src['businessKey']}") cache.delete(f"ordinis:{src['dictionary']}:byKey:{src['businessKey']}")
``` ```
См. design doc `dict-relationships-v2` (внутренний artifact). Spec появится перед deployment v2.
## Stale cache на 5xx / timeout ## Stale cache на 5xx / timeout
+8 -8
View File
@@ -97,9 +97,9 @@ Ordinis следует стандартам REST с расширениями:
| Code | Описание | | Code | Описание |
|---|---| |---|---|
| `internal_unexpected` | Server bug — escalate с traceId | | `internal_unexpected` | Server bug — escalate с traceId |
| `db_unavailable` | Postgres недоступен | | `db_unavailable` | БД недоступна — retry с long backoff |
| `vault_unavailable` | Vault sealed либо unreachable | | `dependent_unavailable` | Зависимый сервис (config, secrets) недоступен — retry |
| `kafka_publish_failed` | Outbox не смог publish (но запись в БД OK — eventually consistent) | | `event_publish_failed` | Запись в БД ОК, но события не отправлены — eventually consistent (events доедут позже) |
## Rate limits ## Rate limits
@@ -114,9 +114,8 @@ limits через resource constraints:
| Concurrent JWT verifications | bounded heap (~5k/sec практический максимум) | | Concurrent JWT verifications | bounded heap (~5k/sec практический максимум) |
При превышении (типично DDoS-like patterns): При превышении (типично DDoS-like patterns):
- HTTP `429 Too Many Requests` после ingress rate limit (ingress nginx default). - HTTP `429 Too Many Requests` после ingress rate limit.
- HTTP `503 Service Unavailable` при exhausting connection pool. - HTTP `503 Service Unavailable` при exhausting connection pool.
- `5xx error rate spike` → alert `OrdinisReadApiErrorBudgetBurnFast`.
### Recommended client-side limits ### Recommended client-side limits
@@ -155,13 +154,14 @@ def retry_with_backoff(
## Tracing ## Tracing
Каждый response имеет `X-Trace-Id` header и `traceId` field в error body. Каждый response имеет `X-Trace-Id` header и `traceId` field в error body.
Ordinis эмитит OpenTelemetry traces в Tempo (`tempo.monitoring:4317`).
При эскалации Ordinis team: При эскалации Ordinis team:
1. Скопируй `traceId` из response либо логов. 1. Скопируй `traceId` из response либо логов.
2. Создай issue с воспроизведением. 2. Создай issue с воспроизведением.
3. Ordinis team query'ит Tempo: `traceId="abc123-..."`. 3. Ordinis team поднимет trace по этому ID — full path запроса виден.
4. Trace покажет full path: ingress → read-api → postgres → response.
Без `traceId` investigation занимает существенно больше времени —
обязательно сохраняй его при ошибках.
## Дальше ## Дальше
+28 -56
View File
@@ -12,53 +12,27 @@ Ordinis публикует все изменения справочников в
| `ordinis.cuod.events.internal` | INTERNAL | INTERNAL словари — нужна role `ordinis-internal`+. | | `ordinis.cuod.events.internal` | INTERNAL | INTERNAL словари — нужна role `ordinis-internal`+. |
| `ordinis.cuod.events.restricted` | RESTRICTED | RESTRICTED — role `ordinis-restricted`. | | `ordinis.cuod.events.restricted` | RESTRICTED | RESTRICTED — role `ordinis-restricted`. |
Topology: 1 broker (single-node на cluster.142), 3+ partitions per topic Параметры топиков (для consumer config):
(historical setup имел 12 partitions, при cluster recreate 2026-05-07
spec задал 3; existing topics annotated `strimzi.io/managed=false`, - `cleanup.policy: delete`
broker partitions сохранены), `cleanup.policy: delete`, - `retention.ms: 604800000` (7 дней)
`retention.ms: 604800000` (7 дней). - Multi-partition (точное число — спросить Ordinis team при registration,
partition count может меняться по operational reasons).
## Подключение ## Подключение
### Адреса bootstrap ### Bootstrap address + creds
| Среда | Bootstrap servers | Auth | Запросить у Ordinis team:
|---|---|---| - Bootstrap servers (внутренний DNS на per-environment basis)
| prod | `ordinis-kafka-kafka-bootstrap.ordinis-prod:9092` (TLS+SASL: `:9093`) | SCRAM-SHA-512 | - SASL credentials (SCRAM-SHA-512 username + password)
| staging | `ordinis-kafka-kafka-bootstrap.ordinis-staging:9092` | SCRAM-SHA-512 | - Topic ACL (Read + Describe на нужные топики)
- Consumer group prefix (для group ACL)
### Получение SASL креденшелов Auth: **SASL/SCRAM-SHA-512**. TLS опциональный (отдельный listener).
Запросить у Ordinis team создание Kafka user (Strimzi `KafkaUser` CR): Креденшелы выдаются per-consumer service. Не share между сервисами —
ACL и audit за каждым.
```yaml
apiVersion: kafka.strimzi.io/v1beta2
kind: KafkaUser
metadata:
name: altum-consumer
namespace: ordinis-prod
labels:
strimzi.io/cluster: ordinis-kafka
spec:
authentication:
type: scram-sha-512
authorization:
type: simple
acls:
- resource:
type: topic
name: ordinis.cuod.events.public
patternType: literal
operations: [Read, Describe]
- resource:
type: group
name: altum-consumer-
patternType: prefix
operations: [Read]
```
Strimzi user-operator создаст Secret `altum-consumer` с полями `username`,
`password`. Используй их в Kafka client config.
## Schema события ## Schema события
@@ -143,18 +117,17 @@ each businessKey изолирован.
## At-least-once семантика ## At-least-once семантика
Outbox pattern гарантирует **at-least-once** delivery: Ordinis использует **outbox pattern** — изменение БД и публикация в Kafka
атомарны через transactional outbox. Гарантия: **at-least-once**.
1. Writer пишет запись в БД + outbox table в одной транзакции.
2. OutboxPoller (scheduled @1s) читает unpublished rows, отправляет в Kafka.
3. После Kafka ACK — отмечает row как published (`published_at` set).
Что это значит для consumer'а: Что это значит для consumer'а:
- Можешь получить **дубликат** события если poller crashed после ACK но до - Можешь получить **дубликат** события (rare, при partial failures на
`published_at` write. Idempotency у consumer'а — must. стороне Ordinis). Idempotency у consumer'а — must.
- При network partition между Kafka и poller — сообщения накапливаются в - При недоступности Kafka события накапливаются в Ordinis side и
outbox (alert `OrdinisOutboxLagHigh` срабатывает > 100 events). доставляются после восстановления — **events не теряются**.
- Order — гарантирован per partition key (`dictionary:businessKey`),
не глобально.
## Idempotency ## Idempotency
@@ -167,13 +140,12 @@ if not seen:
redis.setex(f"ordinis:event:...", 86400, "1") redis.setex(f"ordinis:event:...", 86400, "1")
``` ```
## DLQ (Dead-letter queue) ## Persistent failures (Ordinis side)
Если событие не публикуется > 1000 retry попыток (network issues, Если событие не публикуется > 1000 retry попыток (multi-day Kafka outage),
Kafka unavailable) — переходит в DLQ table в Ordinis. Не теряется, но оно складывается в DLQ внутри Ordinis. Не теряется, но требует ручного
требует ручного разбора. разбора Ordinis team. Consumer не видит таких событий до их
re-publishing — но это очень редкий сценарий.
Симптом: alert `OrdinisOutboxDLQNonEmpty` → check `/api/v1/admin/audit?action=outbox.dlq`.
## Топик для discovery ## Топик для discovery
+7 -18
View File
@@ -50,8 +50,7 @@ Webhook URLs валидируются перед отправкой:
- **Запрещено:** non-HTTPS scheme. Только `https://`. - **Запрещено:** non-HTTPS scheme. Только `https://`.
- **Запрещено:** URL > 2048 chars. - **Запрещено:** URL > 2048 chars.
Метрика `nsi_webhook_ssrf_rejected_total` increments при попытках. См. При нарушении registration возвращает 422 + список причин.
prometheus rule `OrdinisWebhookSsrfRejected`.
## Payload ## Payload
@@ -155,10 +154,11 @@ Consumer должен ответить `2xx` в течение 10 секунд:
| 4 | через 10 мин | | 4 | через 10 мин |
| 5 | через 1ч | | 5 | через 1ч |
| 6-10 | каждые 6ч | | 6-10 | каждые 6ч |
| 1000+ | DLQ → ручной разбор | | После всех попыток | Ordinis team помечает delivery как failed для ручного разбора |
После 1000 неудач delivery попадает в DLQ (`/api/v1/admin/webhooks/deliveries/dlq`). Если consumer недоступен > 1000 retry попыток — Ordinis team будет
Alert: `OrdinisWebhookDLQNonEmpty`. расследовать. Долгосрочное unavailability стоит coordinate (planned
maintenance window) чтобы не накапливать orphan deliveries.
## Test ping ## Test ping
@@ -196,19 +196,8 @@ Response:
сложить в очередь. сложить в очередь.
3. **HMAC verify первым** — до парсинга JSON. 3. **HMAC verify первым** — до парсинга JSON.
4. **Metrics** — track received / processed / errors для своего side. 4. **Metrics** — track received / processed / errors для своего side.
Сравнивай с Ordinis metrics при расследовании. 5. **Whitelist Ordinis IP** на firewall если строгие правила. Точный
5. **Whitelist Ordinis IP** на firewall если строгие правила. IP cluster.142 адрес запросить у Ordinis team при registration.
= `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 мин |
## Дальше ## Дальше
+18 -16
View File
@@ -67,7 +67,7 @@ PUBLIC scope, ReferenceValidator возвращает тот же error
(`x_references_target_not_found`) что и на real not-found. Ordinis **не leak'ает** (`x_references_target_not_found`) что и на real not-found. Ordinis **не leak'ает**
existence of out-of-scope dictionary через differential errors. existence of out-of-scope dictionary через differential errors.
## Orphan reference scanner ## Orphan references
Возможна ситуация когда referenced record **закрывается**, а referencing Возможна ситуация когда referenced record **закрывается**, а referencing
record остаётся: record остаётся:
@@ -78,19 +78,20 @@ record остаётся:
3. spacecraft.RESURS-P-3 теперь имеет dangling FK 3. spacecraft.RESURS-P-3 теперь имеет dangling FK
``` ```
Periodic scanner (`OrphanReferenceScanner`, runs каждые 5 мин) находит Ordinis периодически детектирует такие orphan'ы серверной стороной — данные
такие orphan'ы и emit'ит metric: **не корректируются автоматически** (может быть intentional state до
update referencing записей), но Ordinis team получает уведомление и
может coordinate fix с data stewards.
``` Consumer-side defensiveness:
nsi_orphan_references_total{source_dict="spacecraft",target_dict="satellite_type"} 1 - При получении event `record.closed` для записи на которую ты ссылаешься —
``` invalidate свой cache + либо fetch свежую версию referencing
записи (если она тоже обновилась), либо handle dangling FK явно.
Alert `OrdinisOrphanReferences` срабатывает при > 0 за 10 мин.
## Cascade rules — roadmap (v2) ## Cascade rules — roadmap (v2)
Сегодня закрытие источника НЕ closes referencing automatically. Это Сегодня закрытие источника НЕ closes referencing automatically. Это
дolжно явно делаться через bulk close либо individual updates. дoлжно явно делаться через bulk close либо individual updates.
В v2 планируется schema extension `x-references-on-close`: В v2 планируется schema extension `x-references-on-close`:
@@ -106,21 +107,22 @@ Alert `OrdinisOrphanReferences` срабатывает при > 0 за 10 мин
Опции: Опции:
- **block** — закрытие источника невозможно пока есть active referencing (default). - **block** — закрытие источника невозможно пока есть active referencing (default).
- **warn** — закрытие проходит, в response warning + metric `nsi_cascade_warnings_total`. - **warn** — закрытие проходит, в response warning.
- **cascade** — все referencing закрываются в той же транзакции. - **cascade** — все referencing закрываются в той же транзакции, отдельные
events emit'ятся per record.
См. design doc `dict-relationships-v2` (внутренний artifact в Конкретный timeline и финальная семантика будут опубликованы перед
`~/.gstack/projects/claude/`) для полного scope. deployment v2 — следи за анонсами от Ordinis team.
## Lineage discovery — roadmap (v2) ## Lineage discovery — roadmap (v2)
Сегодня нет endpoint для "что depends on X". Admin должен вручную проходить Сегодня нет endpoint для "что depends on X". В v2 планируется:
schemas чтобы найти ссылки. В v2 планируется:
- `GET /api/v1/dictionaries/{name}/dependents` — словари которые ссылаются. - `GET /api/v1/dictionaries/{name}/dependents` — словари которые ссылаются.
- `GET /api/v1/records/{dict}/{key}/dependents` — конкретные записи. - `GET /api/v1/records/{dict}/{key}/dependents` — конкретные записи.
См. design выше для подробностей. Это позволит consumer'ам и UI узнавать связи без чтения всех schemas
руками. Spec будет опубликован перед deployment v2.
## Best practices ## Best practices
+1 -21
View File
@@ -1,4 +1,4 @@
title: Ordinis НСИ ЦУОД ОДХ title: Ordinis НСИ ЦУОД ОДХ — Integrator Guide
href: index.md href: index.md
items: items:
@@ -34,23 +34,3 @@ items:
items: items:
- name: Альтум — заказ съёмки - name: Альтум — заказ съёмки
href: integration/altum-imaging-order.md href: integration/altum-imaging-order.md
- name: Operations
items:
- name: Runbook (индекс)
href: ops/README.md
- name: SLO
href: ops/slo.md
- name: Outbox DLQ
href: ops/outbox-dlq.md
- name: Database migrations
href: ops/db-migrations.md
- name: Vault unseal
href: ops/vault-unseal.md
- name: Kafka restart
href: ops/kafka-restart.md
- name: Архитектура (legacy)
items:
- name: API integration (старая страница, см. /integration/api/)
href: tech/api-integration.md