Merge branch 'docs/v2-integrator' into 'main'
docs(integrator): v2.x.x update — workflow API + full changelog + schema extensions See merge request 2-6/2-6-4/terravault/ordinis!118
This commit is contained in:
@@ -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` (одна запись —
|
||||
|
||||
@@ -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 НЕ делает
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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 валидируются перед отправкой:
|
||||
|
||||
@@ -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
|
||||
|
||||
+3
-1
@@ -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: Прикладные сценарии
|
||||
|
||||
Reference in New Issue
Block a user