POST /api/support/kb/articles/{id}/archive — Снять статью базы знаний с учёта
Оператор/супервизор Службы заботы снимает статью с учёта — финальная архивация через soft-delete
(DeletedAt) статьи KnowledgeBaseArticleEntity
(матрица 4.7 / 7.1–7.2). Отличается от publish-kb-article (toggle статуса
Published ↔ Archived): здесь статья выводится из справочника целиком — после операции она исчезает и из
публичной выдачи, и из back-office-списка (list-kb-articles показывает только живые
статьи). Соответствует терминальному переходу Archived → SoftDelete (DeletedAt)
(knowledge-base-article.md). Restrict-семантика: снять с учёта
Published-статью нельзя — сначала снятие с публикации (publish=false).
Назначение
Дать Службе заботы штатно вывести неактуальную статью из базы знаний без потери истории (soft-delete,
восстановимо администратором/миграцией). Разрешено из Draft/Archived (непубличные статусы); Published
блокируется до снятия с публикации.
| Поле | Значение |
|---|
| Сервис/контекст | support |
| API area | support (наблюдатель/оператор/супервизор, platform-scope) |
| Feature group | knowledge-base |
| Статус | draft |
| Документ | docs/06-services/support/api/support/archive-kb-article.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | User — Оператор/Супервизор Службы заботы (platform-scope сотрудник ПИН) |
| Auth policy / Permissions | support.kb.manage (CRUD/publish/archive статей; permission.md) |
| Scope (RBAC) | platform-scope (ADR-0052) — не агентство/застройщик-tenant |
| Record-level | не применяется (справочник без owner-а; управляет любой обладатель support.kb.manage) |
| Tenant isolation | TenantId = PIN CompanyId в каждом запросе; статья проверяется на принадлежность tenant |
| Audit | delete (soft): DeletedAt/DeletedByUserId + аудит + Version; PII нет (контент справочный) |
HTTP-контракт
| Поле | Значение |
|---|
| Method | POST |
| Route | /api/support/kb/articles/{id}/archive |
| Success status | 200 (снята с учёта; тело — итоговое состояние) |
| Idempotency header | не применяется (повтор на уже снятой статье → 404, шаг 3) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
TenantId | identity claims | Guid | Да | Required; = PIN CompanyId | Tenant isolation |
ArticleId | route {id} | Guid | Да | != Guid.Empty; существующая не снятая статья (шаг 3) | — |
ActorUserId | identity claims | Guid | Да | оператор с support.kb.manage; пишется в DeletedByUserId | — |
Тело запроса пустое. Восстановление снятой статьи в первой версии — вне API (администратор/миграция).
Модель ответа ArchiveKbArticleResult
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
ArticleId | Guid | Да | KnowledgeBaseArticleEntity.Id | — | Идентификатор снятой статьи. |
Slug | string | Да | .Slug | — | URL-суффикс/код (/help/{slug}). |
PreviousStatus | KbStatus (enum строкой) | Да | .Status до снятия | — | Статус на момент снятия (Draft/Archived). |
DeletedAt | DateTimeOffset | Да | .DeletedAt | — | Момент снятия с учёта (soft-delete). |
Алгоритм
- Контекст и доступ. Операцию выполняет оператор/супервизор Службы заботы (
ActorUserId из claims) с
правом support.kb.manage (иначе 403). support — единый платформенный tenant (TenantId = PIN CompanyId).
- Проверка входа.
ArticleId не пустой; иначе → VALIDATION_FAILED. Прочих полей нет.
- Существование и принадлежность. Статья ищется среди живых (удалённые исключены); не найдена (в т.ч.
уже снятая или чужой tenant) →
SUPPORT_KB_ARTICLE_NOT_FOUND (404). Принадлежность tenant обязательна
(иначе тот же SUPPORT_KB_ARTICLE_NOT_FOUND, platform-scope
ADR-0052). Дочерних сущностей нет.
- Снятие с учёта.
- restrict-guard: статья не должна быть
Published; Published → SUPPORT_KB_ARCHIVE_REQUIRES_UNPUBLISH
(409 — сначала снять с публикации через publish-kb-article publish=false);
- снятие из
Draft/Archived (соответствует переходу Archived → SoftDelete; Draft также непубличен
— безопасно): статус не меняется, ставится soft-delete;
- фиксируется
DeletedAt = now, DeletedByUserId = ActorUserId, UpdatedAt = now, Version += 1;
Status/PublishedAt/Slug/ViewsCount сохраняются (историческая правда).
- Согласованность. Soft-delete фиксируется одним согласованным изменением. Интеграционных Kafka-событий
нет: база знаний — внутренний справочник (knowledge-base-article.md,
README «Events И Consumers»); факт снятия отражают
DeletedAt/аудит/Version. Конкурентная гонка
редакторов/публикаций → SUPPORT_KB_CONCURRENCY_CONFLICT (409, перечитать).
- Результат. Возвращаются
ArticleId, Slug, PreviousStatus, DeletedAt → HTTP 200.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|
| Integration events | none (справочник; Kafka/outbox не задействуются) |
| External calls | none |
| Cache / projections / search | статья исчезает из public FTS/get (search-kb-articles/get-kb-article, фильтр Published + deleted_at is null) и из back-office list-kb-articles; GIN ix_kb_search_vector перестаёт её отдавать после commit (eventual) |
| Notifications | none |
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
VALIDATION_FAILED | 400 | ArticleId = Guid.Empty | «Проверьте идентификатор статьи» | no retry |
FORBIDDEN | 403 | нет support.kb.manage | «Недостаточно прав» | no retry |
SUPPORT_KB_ARTICLE_NOT_FOUND | 404 | статья не найдена / уже снята (DeletedAt) / чужой tenant | «Статья не найдена» | no retry |
SUPPORT_KB_ARCHIVE_REQUIRES_UNPUBLISH | 409 | статья Published (restrict — сначала снять с публикации) | «Сначала снимите статью с публикации» | no retry (unpublish → повторить) |
SUPPORT_KB_CONCURRENCY_CONFLICT | 409 | рассинхрон RowVersion (гонка редакторов/публикаций) | «Статья изменена, повторите» | retry (перечитать) |
Acceptance criteria
- AC-1 (снятие непубличной статьи). Given статья
Draft или Archived в своём tenant, оператор с
support.kb.manage; When POST .../kb/articles/{id}/archive; Then 200: DeletedAt = now,
DeletedByUserId = actor, Version += 1, Status/PublishedAt/Slug/ViewsCount сохранены
(историческая правда); статья исчезает из public-выдачи и back-office-списка. Интеграционных событий нет.
- AC-2 (Published блокируется). Given статья
Published; When снятие с учёта; Then
SUPPORT_KB_ARCHIVE_REQUIRES_UNPUBLISH (409) — сначала снять с публикации (publish=false).
- AC-3 (повторное снятие). Given статья уже снята (
DeletedAt проставлен); When снятие; Then
SUPPORT_KB_ARTICLE_NOT_FOUND (404) — среди живых её нет.
- AC-4 (чужой tenant/нет). Given статья другого tenant или несуществующая; When снятие; Then
SUPPORT_KB_ARTICLE_NOT_FOUND (404, без раскрытия, platform-scope).
- AC-5 (нет прав). Given актор без
support.kb.manage; When снятие; Then FORBIDDEN
(403).
- AC-6 (конкуренция). Given параллельная правка/публикация сменила
RowVersion; When снятие;
Then SUPPORT_KB_CONCURRENCY_CONFLICT (409), перечитать и повторить.
Совместимость и наблюдаемость
- Операция терминальная и внутренняя; внешних контрактов не меняет.
PreviousStatus — enum KbStatus
строкой; новые значения enum — additive; удаление/переименование enum или полей ответа — breaking
(migration + rollout). Разграничение с publish-kb-article: unpublish меняет статус
(Published → Archived), archive — снимает с учёта (DeletedAt).
- Logs:
kb-article archived (ArticleId, Slug, PreviousStatus, actor; PII нет); metrics:
support_kb_archived_total{from_status}, latency p95 < 500 мс (SLO README); traces — OTel;
dashboard Grafana pin-support; runbook — docs/06-services/support/runbooks/
(будущий путь по конвенции).
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы