diff --git a/docs/integration/api/events.md b/docs/integration/api/events.md index 11ac5f3..4f47035 100644 --- a/docs/integration/api/events.md +++ b/docs/integration/api/events.md @@ -97,6 +97,37 @@ artifact в `~/.gstack/projects/claude/`) на каждую закрытую з `cascadeSource` = `{dictionary, businessKey}` источника. Consumer может trace cascade chain. +### Definition events + +Изменения **самой schema** dictionary (не records) emit'ятся отдельными +event types: + +| `eventType` | Когда | +|---|---| +| `DefinitionCreated` | Новый dictionary зарегистрирован | +| `DefinitionUpdated` | Inline PATCH schema (см. `/dictionaries/{name}/schema`) | + +Payload содержит `schemaJson`, `schemaVersion`, `previousSchema` (для +updated). Consumer'ы которые кэшируют schemas инвалидируют по этим +событиям. + +### Schema workflow events (v2.2.0+) + +Workflow draft lifecycle emit'ит дополнительные events. Все содержат +`draftId`, `dictionaryName`, `status`, `branchedFromVersion`, `makerId` +плюс event-specific extras: + +| `eventType` | Trigger | Extras | +|---|---|---| +| `SchemaDraftCreated` | maker создаёт draft | `fromVersion` | +| `SchemaDraftReviewRequested` | submit на review | `reviewers[]`, `note` | +| `SchemaDraftDecided` | reviewer вынес verdict | `decision` (approve/request_changes/reject), `reviewerId`, `comment` | +| `SchemaDraftPublished` | publisher выпустил draft → live | `newVersion`, `publishedAt`, `publishNote` | +| `SchemaDraftWithdrawn` | maker отозвал draft | (без extras) | + +Webhook router маппит эти типы в `dict.draft.*` category (см. +[webhooks.md](webhooks.md)). Полная state machine: [schema-workflow.md](schema-workflow.md). + ## Partition key Сообщения партиционируются по `dictionary + businessKey` (одна запись — diff --git a/docs/integration/api/index.md b/docs/integration/api/index.md index 421951e..03e406c 100644 --- a/docs/integration/api/index.md +++ b/docs/integration/api/index.md @@ -26,6 +26,10 @@ - **Webhooks** — push-уведомления HTTP к зарегистрированным URLs (для consumers без Kafka access). - **Bulk operations** — multi-select close + CSV export. +- **Schema workflow** — maker-checker approval для изменения schemas + (drafts + review + publish). См. [Schema workflow API](schema-workflow.md). +- **Full changelog timeline** — `/dictionaries/{name}/changelog` объединяет + definition CRUD + draft workflow events с `kind` field для UI rendering. ## Что Ordinis НЕ делает diff --git a/docs/integration/api/pending-endpoints.md b/docs/integration/api/pending-endpoints.md index 09b8efe..d1344f0 100644 --- a/docs/integration/api/pending-endpoints.md +++ b/docs/integration/api/pending-endpoints.md @@ -1,28 +1,34 @@ -# Pending endpoints — frontend wishlist +# Pending endpoints — frontend wishlist (ARCHIVED — все 5 shipped) -Эти 5 endpoint'ов нужны admin-UI для завершения UI-фич, заплейсхолженных в -sprint UI polish (MR !74→!93, mai-2026). Каждый блокирует конкретный -component на frontend — сегодня там показывается placeholder ("backend -changelog pending" notice / disabled button / `0` count). +> **Историческая записка.** Этот документ собирал brief из 5 endpoint'ов +> которые блокировали admin-UI fичи в sprint UI polish (MR !74→!93, +> май 2026). Все 5 теперь shipped — раздел остаётся для контекста и +> ссылок из коммитов, но для актуальной API-документации смотри: +> [read-endpoints.md](read-endpoints.md), [schema-workflow.md](schema-workflow.md), +> [events.md](events.md). -Frontend готов подключить через Orval — нужны только OpenAPI спеки. -Приоритет в порядке user-impact: 1 (highest) → 5 (nice-to-have). - -## Status (2026-05-11) +## Status (2026-05-12 — final) | # | Endpoint | Status | |---|---|---| -| 1 | `GET /dictionaries/{name}/changelog` | ✅ shipped (MR !96) | +| 1 | `GET /dictionaries/{name}/changelog` | ✅ shipped (MR !96 + расширен MR !116 — workflow events) | | 2 | `GET /dictionaries/{name}/snapshots` | ✅ shipped (MR !97) | -| 3 | Workflow API (4 endpoints) | ⏸ deferred — separate design + sprint | +| 3 | Workflow API (8 endpoints) | ✅ shipped (MR !113 в v2.2.0) → [schema-workflow.md](schema-workflow.md) | | 4 | `PATCH /dictionaries/{name}/schema` | ✅ shipped (MR !98) | | 5 | `GET /dictionaries/graph/outgoing` | ✅ shipped (MR !95) | -**4/5 endpoints landed** в session 2026-05-11 backend sprint. Workflow API (#3) -требует отдельной design discussion — state machine + RBAC pool model -parallel record-level drafts (см. `DraftController.java` для record-level -draft architecture как reference). Frontend WorkflowBanner currently рендерит -placeholder banner — кнопки скрыты пока endpoints не shipped. +**5/5 endpoints landed.** Brief полностью закрыт. + +- **Changelog** в MR !116 расширен event types для draft workflow + (`SCHEMA_DRAFT_CREATED`, `REVIEW_REQUESTED`, `DECIDED`, `PUBLISHED`, + `WITHDRAWN`). Frontend History tab теперь показывает полный timeline + с промежуточными draft стадиями. Entries имеют поле `kind` для рендера + icon/color по типу события. +- **Workflow API** (#3) развернулось в 8 endpoints (4 core + 4 read-only): + create draft / submit review / decide / publish + list / active / get / pool + queue. Liquibase 0022 добавил таблицу `dictionary_schema_drafts` со + state machine guards (EXCLUDE on active, no-self-approve check). + Полная спецификация: [schema-workflow.md](schema-workflow.md). --- diff --git a/docs/integration/api/schema-workflow.md b/docs/integration/api/schema-workflow.md new file mode 100644 index 0000000..887807f --- /dev/null +++ b/docs/integration/api/schema-workflow.md @@ -0,0 +1,420 @@ +# Schema workflow API (drafts / review / publish) + +Maker-checker approval workflow для schema-level изменений. +Параллельная архитектура к record-level `DraftController` (см. +[bitemporal.md](bitemporal.md)), но drafts содержат **полное proposed +JSON Schema** dictionary'а, а не отдельные записи. + +Доступно с **v2.2.0**. + +## Зачем + +Schema изменения (добавить поле, поменять enum, поменять `x-references`) +часто требуют согласования: maker готовит изменение, reviewer одобряет, +publisher выпускает новую semver. Workflow API даёт state machine для +этого процесса с audit trail в `audit_log` + Kafka outbox events +(`SchemaDraft*`) + lock на concurrent drafts. + +Альтернатива workflow: inline `PATCH /api/v1/dictionaries/{name}/schema` +для **non-breaking** изменений (см. [read-endpoints.md](read-endpoints.md)). +Если PATCH возвращает 422 `breaking_change_requires_draft` — переходи на +workflow. + +## State machine + +``` + ┌──────────────────────────────────┐ + ▼ │ + ┌──────────────┐ │ + create ──▶│ DRAFT │──── withdraw ────┐ │ + └──────┬───────┘ │ │ + │ submit_review │ │ + ▼ │ │ + ┌──────────────────┐ │ │ + │ REVIEW_PENDING │── withdraw ────┤ │ + └──────┬───┬───┬───┘ │ │ + approve│ │reject │ │ + │ └─request_changes─┐ │ │ + ▼ ▼ ▼ │ + ┌──────────┐ ┌─────────────────┐ │ + │ APPROVED │ │CHANGES_REQUESTED│────┘ + └─────┬────┘ └───────┬─────────┘ re-submit + publish │ │ + ▼ ▼ withdraw + ┌─────────────┐ ┌───────────┐ + │ PUBLISHED │ │ WITHDRAWN │ + │ (terminal) │ │ (terminal)│ + └─────────────┘ └───────────┘ + ┌───────────┐ + │ REJECTED │ (terminal — от reject) + └───────────┘ +``` + +**Терминальные** статусы: `PUBLISHED`, `REJECTED`, `WITHDRAWN` — изменению +дальше не подлежат, но остаются в БД для audit trail. + +**Invariants** (enforced на уровне Liquibase 0022 + service): +- Maximum один active draft per dictionary + (`schema_drafts_one_active_per_dict` EXCLUDE constraint покрывает + `draft|review_pending|changes_requested|approved`). +- Self-approve forbidden (`schema_drafts_no_self_approve`: `maker_id != + decision_user_id`). +- Optimistic version lock на create + publish — `fromVersion` / + `expectedHeadVersion` сверяется с текущим `definition.schemaVersion`, + если разошлось → 409 `version_mismatch`. + +## Endpoints + +### Создание / переходы + +#### `POST /api/v1/dictionaries/{name}/drafts` + +Создаёт draft от текущего HEAD schema. Status = `DRAFT`. + +Request: +```json +{ + "fromVersion": "1.4.0", + "reason": "добавить optional поле altitude", + "proposedSchema": { "type": "object", "properties": { ... } } +} +``` + +Response 201 (см. [Полный response shape](#полный-response-shape) ниже): +```json +{ + "draftId": "8f3...", + "dictionaryName": "satellite", + "status": "draft", + "branchedFromVersion": "1.4.0", + ... +} +``` + +Errors: +- 409 `version_mismatch` — `fromVersion` не = current HEAD. +- 409 `active_draft_exists` — другой draft уже active на этом dict'е + (терминальные не блокируют). + +--- + +#### `POST /api/v1/dictionaries/{name}/drafts/{draftId}/review` + +Maker submit'ит draft → status `REVIEW_PENDING`. +Работает из `DRAFT` (initial submit) и `CHANGES_REQUESTED` (re-submit +после доработки). + +Request: +```json +{ + "reviewers": ["u-17", "u-92"], // optional — NULL = open pool (anyone with approve role) + "note": "проверьте границы altitude" +} +``` + +Response 202: +```json +{ "status": "review_pending", ... } +``` + +Errors: +- 403 `maker_only` — actor != maker. +- 409 `invalid_transition` — draft не в DRAFT или CHANGES_REQUESTED. + +--- + +#### `POST /api/v1/dictionaries/{name}/drafts/{draftId}/decision` + +Reviewer's decision. + +Request: +```json +{ + "decision": "APPROVE", // или "REQUEST_CHANGES" или "REJECT" + "comment": "OK по полю, но опечатка в описании" +} +``` + +Verdict mapping: +- `APPROVE` → status `APPROVED` (готов к publish) +- `REQUEST_CHANGES` → status `CHANGES_REQUESTED` (maker должен доработать) +- `REJECT` → status `REJECTED` (terminal) + +Response 200 — `SchemaDraftResponse` с updated status. + +Errors: +- 403 `self_approve_forbidden` — `actor == maker`. +- 409 `invalid_transition` — draft не в `REVIEW_PENDING`. + +--- + +#### `POST /api/v1/dictionaries/{name}/drafts/{draftId}/publish` + +Publish approved draft → live HEAD. Bump schema version (minor default, +major на breaking changes — type change OR required removal в top-level +properties). + +Request: +```json +{ + "expectedHeadVersion": "1.4.0", + "publishNote": "v1.5.0 — добавил altitude" +} +``` + +Response 200: +```json +{ + "draft": { "status": "published", ... }, + "newVersion": "1.5.0", + "publishedAt": "2026-05-12T22:00:00Z" +} +``` + +Side effects: +- `dictionary_definitions.schema_json` и `.schema_version` обновляются + в одной SERIALIZABLE транзакции. +- Audit log: `SCHEMA_DRAFT_PUBLISHED` (`DRAFT_PUBLISH` action). +- Outbox event: `SchemaDraftPublished`. +- WAL/Kafka: subscribers получают definition update в обычном порядке. + +Errors: +- 409 `invalid_transition` — draft не в `APPROVED`. +- 409 `version_mismatch` — `expectedHeadVersion` stale (кто-то другой + опубликовал свой draft первым). Refetch + retry. + +--- + +#### `DELETE /api/v1/dictionaries/{name}/drafts/{draftId}` + +Maker отзывает свой draft. Status → `WITHDRAWN` (terminal). +Работает из любого non-terminal статуса. + +Errors: +- 403 `maker_only` — actor != maker. +- 409 `invalid_transition` — draft уже в terminal статусе. + +### Read-only + +#### `GET /api/v1/dictionaries/{name}/drafts` + +List всех drafts на dict (newest first). Включает terminal — для audit +trail UI. + +#### `GET /api/v1/dictionaries/{name}/drafts/active` + +Convenience: возвращает текущий **active** draft (любой non-terminal статус) +для UI workflow banner. **404 `no_active_draft`** если нет — это нормальный +сигнал, фронт показывает «no draft» состояние. + +#### `GET /api/v1/dictionaries/{name}/drafts/{draftId}` + +Single draft по id (drilling в reviewer queue). + +#### `GET /api/v1/admin/schema-reviews/pending` + +Global pool queue для approvers — все `review_pending` drafts across all +dicts, sorted oldest-first (fairness). Paginated: + +``` +?page=0&size=50 +``` + +Response: +```json +{ + "items": [ { "draftId": "...", "dictionaryName": "satellite", ... } ], + "page": 0, + "size": 50, + "totalElements": 7, + "totalPages": 1 +} +``` + +## Полный response shape + +`SchemaDraftResponse`: + +```json +{ + "draftId": "8f3a...", + "dictionaryId": "5fe1...", + "dictionaryName": "satellite", + "status": "review_pending", // см. State machine + "branchedFromVersion": "1.4.0", + "proposedSchema": { ... }, // full JSON Schema + "reason": "добавить altitude", // optional + "makerId": "u-42", + "createdAt": "2026-05-12T20:00:00Z", + "submittedAt": "2026-05-12T20:05:00Z", // NULL до submitForReview + "reviewers": ["u-17", "u-92"], // NULL = pool + "reviewNote": "проверьте altitude", // optional + "decisionUserId": null, // set после decide + "decisionAt": null, + "decisionComment": null, + "publishedAt": null, // set после publish + "publishedVersion": null, + "publishNote": null, + "version": 1 // optimistic lock counter +} +``` + +## Outbox events + +Каждый transition emit'ит event в Kafka outbox topic `definition.draft` +(routed через webhook subscribers как `dict.draft.*` category). + +| Event type | Trigger | Payload extras | +|---|---|---| +| `SchemaDraftCreated` | POST /drafts | `fromVersion` | +| `SchemaDraftReviewRequested` | POST /review | `reviewers[]`, `note` | +| `SchemaDraftDecided` | POST /decision | `decision`, `reviewerId`, `comment` | +| `SchemaDraftPublished` | POST /publish | `newVersion`, `publishedAt`, `publishNote` | +| `SchemaDraftWithdrawn` | DELETE | (без extras) | + +Envelope: +```json +{ + "eventId": "...", + "occurredAt": "2026-05-12T20:00:00Z", + "schemaVersion": "1.0", + "bundle": "cuod", + "dictionaryName": "satellite", + "scope": "PUBLIC", + "payload": { "draftId": "...", "dictionaryName": "...", "status": "...", ... } +} +``` + +## Audit log + +Параллельно outbox'у каждое transition пишется в `audit_log` через +`AuditLogger.schemaDraftEvent`: + +| `event_type` | `action` | `payload_before` | `payload_after` | +|---|---|---|---| +| `SCHEMA_DRAFT_CREATED` | `DRAFT_CREATE` | live schema | proposed schema | +| `SCHEMA_DRAFT_REVIEW_REQUESTED` | `DRAFT_REVIEW` | live schema | proposed schema | +| `SCHEMA_DRAFT_DECIDED` | `DRAFT_APPROVE` / `DRAFT_REJECT` / `DRAFT_CHANGES_REQUESTED` | live | proposed | +| `SCHEMA_DRAFT_PUBLISHED` | `DRAFT_PUBLISH` | previous live | new live (= proposed) | +| `SCHEMA_DRAFT_WITHDRAWN` | `DRAFT_WITHDRAW` | live | proposed | + +`payload_before` / `payload_after` доступны через `GET /admin/audit` UI +для diff render'а в History timeline. + +Также эти event_types включены в `GET /api/v1/dictionaries/{name}/changelog` +— см. [Full timeline в changelog](#full-timeline-в-changelog) ниже. + +## Full timeline в changelog + +`GET /api/v1/dictionaries/{name}/changelog` после MR !116 возвращает unified +timeline definition-level + workflow draft events. Каждая entry имеет: + +```json +{ + "version": "1.5.0", // только для versioning events + "publishedAt": "2026-05-12T20:23:00Z", + "publishedBy": { "sub": "u-42", "name": "..." }, + "headers": null, // commit msg — не captured пока + "diff": { "added": [...], "removed": [...], "changed": [...] }, + "recordsAffected": 0, + "isCurrent": true, + "kind": "draft_publish" // ← NEW field в MR !116 +} +``` + +Поле `kind`: + +| Value | Source event_type | +|---|---| +| `definition_create` | DEFINITION_CREATED | +| `definition_update` | DEFINITION_UPDATED (inline PATCH path) | +| `draft_create` | SCHEMA_DRAFT_CREATED | +| `draft_review` | SCHEMA_DRAFT_REVIEW_REQUESTED | +| `draft_approve` | SCHEMA_DRAFT_DECIDED + action=DRAFT_APPROVE | +| `draft_reject` | SCHEMA_DRAFT_DECIDED + action=DRAFT_REJECT | +| `draft_changes_requested` | SCHEMA_DRAFT_DECIDED + action=DRAFT_CHANGES_REQUESTED | +| `draft_publish` | SCHEMA_DRAFT_PUBLISHED ← versioning event | +| `draft_withdraw` | SCHEMA_DRAFT_WITHDRAWN | +| `other` | fallback (unknown event_type) | + +**Versioning events** (`definition_create`, `definition_update`, +`draft_publish`) получают `version` label из `dictionary_definitions.schema_version` +для current HEAD, NULL для исторических entries (semver backfill TBD). + +**Promotional events** (`draft_create`, `draft_review`, `draft_approve`, +`draft_reject`, `draft_changes_requested`, `draft_withdraw`) возвращаются +с `version=null` — фронт рендерит как timeline tick между публикациями. + +## Пример workflow + +Полный flow «изменить enum в `mission.status`»: + +```bash +# 0. Получить current HEAD +curl /api/v1/dictionaries/mission +# → schemaVersion: "1.4.0", schemaJson: { ... } + +# 1. Создать draft с новым enum +curl -X POST /api/v1/dictionaries/mission/drafts -d '{ + "fromVersion": "1.4.0", + "reason": "добавить status=deferred", + "proposedSchema": { ... } +}' +# → 201, draftId: "8f3a..." + +# 2. Submit на review +curl -X POST /api/v1/dictionaries/mission/drafts/8f3a.../review -d '{ + "reviewers": ["u-17"], + "note": "deferred — это inactive но не decommissioned" +}' +# → 202, status: "review_pending" + +# 3. Reviewer u-17 (другой actor) одобряет +curl -X POST /api/v1/dictionaries/mission/drafts/8f3a.../decision -d '{ + "decision": "APPROVE", + "comment": "OK" +}' +# → 200, status: "approved" + +# 4. Publisher (любой actor != maker) publish'ит +curl -X POST /api/v1/dictionaries/mission/drafts/8f3a.../publish -d '{ + "expectedHeadVersion": "1.4.0", + "publishNote": "v1.5.0 — status enum +deferred" +}' +# → 200, newVersion: "1.5.0" +``` + +После publish: +- `dictionary_definitions.schema_version` = "1.5.0" +- `dictionary_definitions.schema_json` = новый schema +- `audit_log` строки: SCHEMA_DRAFT_CREATED, REVIEW_REQUESTED, DECIDED + (APPROVE), PUBLISHED +- Kafka outbox: 4 события `SchemaDraft*` +- `changelog` endpoint показывает полную цепочку с `kind` field +- Webhook subscribers получают `dict.draft.*` notifications + +## Error codes (full reference) + +| HTTP | Code | Когда | +|---|---|---| +| 404 | `schema_draft_not_found` | id не существует / scope-hidden | +| 404 | `dictionary_not_found` | dict не существует / scope-hidden | +| 404 | `no_active_draft` | `/drafts/active` нет non-terminal draft'а | +| 409 | `active_draft_exists` | попытка create при существующем active | +| 409 | `version_mismatch` | optimistic lock на create или publish | +| 409 | `invalid_transition` | state machine violation | +| 403 | `self_approve_forbidden` | maker decision на own draft | +| 403 | `maker_only` | submit/withdraw от чужого actor'а | +| 422 | `breaking_change_requires_draft` | inline PATCH вернул это — переходи в workflow | + +## RBAC (TBD) + +В текущей реализации workflow доступен любому authenticated user'у через +network-level auth policy. Когда Keycloak roles claim прорастёт в JWT, +будут отдельные требования: + +- `dict_schema_maker` — для create / submit / withdraw +- `dict_schema_reviewer` — для decision +- `dict_schema_publisher` — для publish + +См. record-level `DraftController` для reference architecture pool model. diff --git a/docs/integration/api/webhooks.md b/docs/integration/api/webhooks.md index 336a650..ebbd2b0 100644 --- a/docs/integration/api/webhooks.md +++ b/docs/integration/api/webhooks.md @@ -39,8 +39,24 @@ curl -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \ | `scopes` | array | Фильтр по scope (subset из доступных consumer'у). | | `dictionaries` | array | Фильтр по dictionary names. `null`/`[]` = все доступные. | | `actions` | array | `created` / `updated` / `closed`. `null` = все. | +| `categories` | array | Event categories: `dict.record.*` / `dict.definition.*` / `dict.draft.*`. `null` = все. (v2.2.0+) | | `active` | bool | Можно временно отключить без удаления. | +### Event categories (v2.2.0+) + +С v2.2.0 webhook subscriber'ы могут фильтровать по семантическим категориям: + +| Category | Маппит outbox events | +|---|---| +| `dict.record.*` | Record CRUD (default до v2.2.0): record.created / .updated / .closed | +| `dict.definition.*` | Schema-level: `DefinitionCreated`, `DefinitionUpdated` (inline PATCH) | +| `dict.draft.*` | Workflow drafts: `SchemaDraftCreated`, `…ReviewRequested`, `…Decided`, `…Published`, `…Withdrawn` | + +Если `categories=null` подписчик получает все (legacy behaviour). +Frontend reviewer dashboards обычно подписываются на `dict.draft.*` +для notifications о новых drafts. Bundle consumers (Альтум, BI) — +на `dict.record.*`. + ## SSRF guard Webhook URLs валидируются перед отправкой: diff --git a/docs/integration/api/x-references.md b/docs/integration/api/x-references.md index f31dcf3..99b372c 100644 --- a/docs/integration/api/x-references.md +++ b/docs/integration/api/x-references.md @@ -138,7 +138,63 @@ deployment v2 — следи за анонсами от Ordinis team. 4. **Document FK в displayName** — `"description": "FK на satellite_type через code"` помогает при ревью schemas. +## Связанные schema extensions + +### `x-localized` — мультиязычные поля + +```json +{ "type": "object", "x-localized": true } +``` + +См. [read-endpoints.md](read-endpoints.md) `?lang=` параметр. + +### `x-section` — bucketing полей в admin-UI form + +Hint для admin-UI: в какую визуальную секцию формы (`identity`, +`references`, `description`, `dates`, `geometry`, `physical`, `extra`, +либо custom string) сгруппировать поле. + +```json +{ + "type": "number", + "x-section": "orbital_geometry" +} +``` + +Если `x-section` не задан, admin-UI применяет эвристику: +- `x-references` → `references` +- `x-localized` → `description` +- `format=date|date-time` → `dates` +- key matches `/^(geo|geom|wkt|lon|lng|lat|altitude|coord)/` → `geometry` +- `required` → `identity` +- else → `extra` + +Predefined IDs получают i18n labels (Связи / Описание / Даты / Геометрия / +Физические параметры / Идентификация / Дополнительно). Custom IDs +humanize'ятся: `orbital_geometry` → `Orbital Geometry`. + +Только UI hint — backend не validate'ит и не reject'ит unknown values. + +### `x-id-source` — авто-derive businessKey + +```json +{ "x-id-source": "code" } +``` + +При CREATE businessKey берётся из `data.code` automatically (UI скрывает +поле). См. ReferenceValidator. + +### `x-unique` — уникальность поля в dict'е + +```json +{ "type": "string", "x-unique": true } +``` + +Backend enforce'ит при CREATE/UPDATE. Используется для `code` полей в +target dicts которые ссылаются по `x-references`. + ## Дальше - [Bundle cuod](bundle-cuod.md) — список текущих FK в проде - [События](events.md) — учитывай cascade closure при инвалидации +- [Schema workflow](schema-workflow.md) — как менять schema через approval flow diff --git a/docs/toc.yaml b/docs/toc.yaml index 0d3b7ca..f34e0b6 100644 --- a/docs/toc.yaml +++ b/docs/toc.yaml @@ -29,7 +29,9 @@ items: href: integration/api/best-practices.md - name: Bundle справочников cuod href: integration/api/bundle-cuod.md - - name: Pending endpoints (admin-UI wishlist) + - name: Schema workflow (drafts / review / publish) + href: integration/api/schema-workflow.md + - name: Pending endpoints (архив, все shipped) href: integration/api/pending-endpoints.md - name: Прикладные сценарии