UC-SUP-004. Ручное вмешательство через permissions
Назначение
Описывает ручное вмешательство Службы заботы в чужие сервисы (матрица 3.x): смена статуса/переназначение
сделки в crm, блокировка пользователя / правка роли в identity, корректировка комиссии в rewards. Ключ
границы: вмешательство выполняется в owning-сервисе через выданные support platform-scope permissions;
сами permissions живут и проверяются в owning-сервисе, support их не определяет и не дублирует. Support
лишь фиксирует факт/ссылку заметкой арбитражного дела (ArbitrageNoteEntity.AuditRefs) — чужие таблицы не
пишет. Связан с контекстами support (+ crm/identity/rewards); агрегат support —
ArbitrageCaseEntity. Покрывает README решение №2.
Metadata
| Поле | Значение |
|---|---|
| ID | UC-SUP-004 |
| Контекст | support (вмешательство — crm/identity/rewards) |
| Тип | основной (+ альтернативные потоки A1–A3) |
| Статус | draft |
Актор
| Поле | Значение |
|---|---|
| Роль | СлужбаЗаботы — Оператор (с выданными интервенционными правами) / Супервизор (финансы/блокировка, 3.6/3.8) |
| Permissions / scope | вмешательство: crm.deals.manage, identity.users.block, identity.role-assignments.manage, rewards.accruals.manage — platform-scope, назначены support-роли, проверяются в owning-сервисе; фиксация — support.arbitrage.manage (ADR-0052) |
| Tenant | вмешательство — кросс-tenant через platform-scope (owning-сервис резолвит TenantId цели); support-заметка — TenantId = PIN CompanyId |
| Record-level | в owning-сервисе — по его правилам; в support — по AssigneeUserId дела |
Предусловие
- Идёт разбор (UC-SUP-003) или обращение, требующее ручной правки в чужом сервисе.
- Оператору назначены нужные интервенционные permissions в platform-scope (SEED-каталог owning-сервисов, не support).
- Целевой ресурс (сделка/пользователь/начисление) существует в owning-сервисе.
- Для фиксации факта существует арбитражное дело в статусе
Registered/UnderInvestigation(иначе заметка запрещена, INV-ARB-5).
Гарантируется после сценария: в owning-сервисе применена ручная правка (его аудит несёт actor =
support-оператор, событие — из его outbox); в support добавлена append-only заметка с типизированными
AuditRefs, ArbitrageCaseEntity.Version++; support не писал чужие таблицы и не публиковал
интеграционных событий по правке.
Триггер
| Поле | Значение |
|---|---|
| Источник | UI button / API call |
| Триггерящий элемент | Карточка дела/тикета Службы заботы со сквозными действиями по чужим сервисам |
Основной Поток
- Оператор в карточке дела определяет необходимое вмешательство (например, конфликт бронирований требует отката статуса сделки).
- Вмешательство в
crm. Оператор →POST /api/pin-manager/crm/deals/{id}/override-status(crm override-deal-status); авторизацияcrm.deals.manageпроверяется вcrm(platform-scope), не в support. crmприменяет override статуса сделки, пишет таймлайн и публикуетpin.crm.deal-stage-changed.v1из своего outbox. Support синхронного ответа ждёт как обычный API-клиент.- Фиксация факта в support. Оператор →
POST /api/support/arbitrage-cases/{id}/notes(add-arbitrage-note) сAuditRefs = [{ Service = "crm", EntityType = "Deal", EntityId, ChangeAt, Note }]. - Добавляется append-only заметка с
AuditRefs(INV-ARB-5); поднимается доменное событиеArbitrageNoteAdded; support не пишет в таблицыcrm/identity/rewards. - Фиксируется только заметка арбитражного дела support; интеграционного Kafka-события support здесь не публикует (событие о правке — на стороне owning-сервиса).
- Аудит: факт вмешательства виден в таймлайне owning-сервиса (actor-stamping = support-оператор) и в
AuditRefsдела; централизованный аудит-лог только читается. - Фронт обновляет карточку дела и (при сквозном просмотре) карточку сделки; стороны узнают об изменении через события owning-сервиса (
notifications).
Альтернативные Потоки
A1. Блокировка/разблокировка пользователя в identity
- Супервизор →
POST /api/v1/companies/my/users/{userId}/block(identity block-company-user) в platform-scope (identity.users.block);identityпубликуетpin.identity.users.v1. - Support фиксирует
AuditRefs = [{ Service = "identity", EntityType = "User", EntityId = userId }]заметкой дела.
Возвращается к основному потоку на шаг 4.
A2. Правка ролей/прав в identity
- Оператор → отзыв/выдача роли (revoke-role-assignment / grant-role-assignment) с
identity.role-assignments.manage(platform-scope). - Изменения прав применяет
identity; support фиксирует ссылку вAuditRefs.
Возвращается к основному потоку на шаг 4.
A3. Корректировка комиссии в rewards
- По решению арбитража (UC-SUP-003) — ручное начисление → rewards create-manual-accrual (
rewards.accruals.manage);rewardsпубликуетpin.rewards.commission-accrual-created.v1. - Support фиксирует
AuditRefs = [{ Service = "rewards", EntityType = "Accrual", EntityId }].
Возвращается к основному потоку на шаг 4.
A4. Owning-guard отклоняет правку
- Правка нарушает бизнес-инвариант owning-сервиса: override терминальной сделки →
CRM_DEAL_ALREADY_TERMINAL(409); повторная блокировка →IDENTITY_USER_ALREADY_BLOCKED(409). Правки не происходит; support заметку не пишет (фиксировать нечего).
Завершается ошибкой owning-сервиса; оператор корректирует намерение.
A5. Owning-сервис недоступен
crm/identity/rewardsнедоступен →CRM_UNAVAILABLE/IDENTITY_UNAVAILABLE(503). Правка не применена, support-заметка не создаётся (нет подтверждённого факта). Оператор повторяет позже; идемпотентность самой правки обеспечивает owning-сервис.
Завершается ошибкой доступности; повтор позже.
A6. Нет интервенционного права в owning-сервисе
- У support-оператора нет назначенного platform-scope permission (
crm.deals.manageи т.п.) →FORBIDDEN(403) из owning-сервиса (не из support). Правки нет; заметка не создаётся.
Завершается отказом доступа owning-сервиса; запросить назначение права.
A7. Фиксация после решения дела
- Дело, к которому привязывается заметка, уже
Resolved/Escalated/Closed→SUPPORT_ARBITRAGE_NOTE_FORBIDDEN(409, INV-ARB-5). Правка в owning-сервисе могла примениться, но факт фиксируется в аудите owning-сервиса; в support заметку добавить нельзя (дело закрыто для записи).
Завершается ошибкой; факт остаётся в аудите owning-сервиса.
Постусловие
- В owning-сервисе применена ручная правка (статус сделки/блок пользователя/роль/начисление); его аудит несёт actor = support-оператор.
- В support добавлена append-only
ArbitrageNoteEntityс типизированнымиAuditRefs;ArbitrageCaseEntity.Version++. - Support чужие таблицы не мутировал и интеграционных событий по правке не публиковал (события — из owning-сервисов).
Возможные Ошибки
| Код | HTTP | Условие | Поведение клиента |
|---|---|---|---|
FORBIDDEN | 403 | нет platform-scope permission в owning-сервисе (crm.deals.manage и т.п.) | Скрыть действие вмешательства |
CRM_DEAL_ALREADY_TERMINAL | 409 | override терминальной сделки (owning-guard) | Показать «сделка завершена» |
IDENTITY_USER_ALREADY_BLOCKED | 409 | повторная блокировка (owning-guard) | Показать текущий статус |
SUPPORT_ARBITRAGE_NOT_FOUND | 404 | дело для фиксации не найдено | Обновить реестр дел |
SUPPORT_ARBITRAGE_NOTE_FORBIDDEN | 409 | заметка после решения (INV-ARB-5) | Заметки только для чтения |
IDENTITY_UNAVAILABLE / CRM_UNAVAILABLE | 503 | owning-сервис недоступен | Повторить позже |
Доменные События
| Событие | Когда поднимается | Subscribers |
|---|---|---|
ArbitrageNoteAdded | ArbitrageCase.AddNote (фиксация факта) | таймлайн дела (интеграционного события support нет) |
DealStatusChanged (в crm) | Deal.OverrideStatus в owning-сервисе | сборщик DealStageChangedEvent (crm) |
UserBlocked (в identity) | блокировка в owning-сервисе | сборщик UserBlockedKafkaEvent (identity) |
Kafka Интеграционные События
Support по факту вмешательства не публикует событий (граница: правка — в owning-сервисе). События исходят из owning-сервисов:
| Событие | Topic | Owning-сервис | Consumers |
|---|---|---|---|
DealStageChangedEvent | pin.crm.deal-stage-changed.v1 | crm (шаг 3) | messenger, notifications |
UserBlockedKafkaEvent | pin.identity.users.v1 | identity (A1) | подписчики users (в т.ч. support реплики) |
CommissionAccrualCreatedEvent | pin.rewards.commission-accrual-created.v1 | rewards (A3) | notifications |
Кросс-сервисные ожидания и согласованность
Ожидает от соседей (вход) — граница ответственности
- Owning-сервис проверяет право и инвариант. Авторизация интервенционного permission (
crm.deals.manage,identity.users.block,identity.role-assignments.manage,rewards.accruals.manage) выполняется в owning-сервисе (platform-scope), не в support. Owning-guards (CRM_DEAL_ALREADY_TERMINAL,IDENTITY_USER_ALREADY_BLOCKED) — тоже на стороне соседа. support вызывает как обычный синхронный API-клиент и ждёт ответа. - Owning-сервис — source of truth правки и её события.
crm/identity/rewardsприменяют изменение, ставят actor = support-оператор в своём аудите (actor-stamping) и публикуют своё интеграционное событие из своего outbox. support этих таблиц не пишет и permissions не дублирует.
Предоставляет соседям (выход)
- По факту вмешательства support событий НЕ публикует (граница: правка — в owning-сервисе). Единственный
эффект в support — append-only
ArbitrageNoteEntityс типизированнымиAuditRefs(Service/EntityType/EntityId/ChangeAt?/Note?) и доменноеArbitrageNoteAdded(внутренний таймлайн, интеграционного события нет).
Последовательность и окно согласованности
- Порядок (важно): сначала синхронная правка в owning-сервисе (шаг 2–3), затем фиксация факта заметкой в support (шаг 4–6). Если правка не подтверждена (guard/недоступность/403) — заметку не пишем (A4–A6): инвариант «фиксируем только состоявшийся факт».
- Событие owning-сервиса (
deal-stage-changed/users/commission-accrual-created) доходит до своих потребителей (messenger/notifications/реплики) асинхронно, окно < 1 мин — именно оно уведомляет стороны об изменении, не support. В промежутке карточка дела в support уже несётAuditRef, а стороны узна́ют об изменении по мере доставки события соседа.
Идемпотентность и конкурентность
- Идемпотентность самой правки — на стороне owning-сервиса (его guards/версии). Повтор support-заметки на тот
же
AuditRefдопустим (append-only фиксирует попытки), но семантически дубль-ссылку арбитр не добавляет. - Заметка после решения дела запрещена (INV-ARB-5, A7); гонка правки в support — оптимистичная блокировка на
ArbitrageCaseEntity(проигравший перечитывает).
Acceptance Criteria
| # | Given (дано) | When (когда) | Then (тогда) |
|---|---|---|---|
| AC1 | support-оператор без crm.deals.manage | вызывает override статуса сделки в crm | FORBIDDEN (403) из crm, не из support; правки нет |
| AC2 | Правка применена в owning-сервисе | вмешательство завершено | support не пишет в таблицы crm/identity/rewards (только своя заметка) |
| AC3 | Правка состоялась, дело UnderInvestigation | оператор фиксирует факт | append-only заметка с типизированными AuditRefs (Service/EntityType/EntityId) |
| AC4 | Вмешательство завершено | проверяется outbox support | support не публикует Kafka-событие по правке; событие исходит из owning-сервиса |
| AC5 | Правка в crm/identity | проверяется таймлайн owning-сервиса | actor = support-оператор (actor-stamping) |
| AC6 | Дело Resolved/Escalated/Closed | оператор добавляет заметку | SUPPORT_ARBITRAGE_NOTE_FORBIDDEN (INV-ARB-5) |
| AC7 | Owning-сервис недоступен или guard отклонил (терминальная сделка/уже заблокирован) | оператор вмешивается | CRM_UNAVAILABLE/IDENTITY_UNAVAILABLE (503) или CRM_DEAL_ALREADY_TERMINAL/IDENTITY_USER_ALREADY_BLOCKED (409); support-заметка не создаётся |
Наблюдаемость и НФТ
- Латентность: синхронный вызов owning-сервиса + фиксация заметки — p95 < 1 с (зависит от соседа);
таймаут/недоступность соседа →
503без записи заметки. - Логи (без PII):
support-intervention-recorded(caseId, service, entityType, entityId, actor) — только ссылка/факт, без тел правок; правку логирует owning-сервис. - Метрики: число вмешательств по типам (
crm/identity/rewards), доля отказов owning-guards/403/503, задержка «правка → фиксация заметки». - Доступ/безопасность: интервенционные permissions живут и проверяются в owning-сервисах (platform-scope),
support их не определяет; фиксация —
support.arbitrage.manage; каждый факт прослеживается через actor-stamping owning-сервиса +AuditRefsдела (двойной след).
Обратные ссылки
Автоссылки: где используется этот документ.
- Use Cases (1): UC-SUP-003. Арбитраж конфликта через аудит
- registries (1): Реестр use cases и user stories PIN
Связанные Документы
- Owning-сервисы: crm override-deal-status, crm move-deal-step, identity block-company-user, identity revoke-role-assignment, rewards create-manual-accrual; permission-каталог — identity permission.md.
- Support: add-arbitrage-note; domain — arbitrage-case.md (
ArbitrageAuditRefs, INV-ARB-5). - Смежные сценарии: UC-SUP-003, UC-CRM-006 (эталон вмешательства).
- Контекст: README; tenancy/кросс-tenant — ADR-0052.