Skip to content

Latest commit

 

History

History
361 lines (277 loc) · 37.2 KB

API.md

File metadata and controls

361 lines (277 loc) · 37.2 KB

API v1 Specification - Wiki

Каталог сквозных проектов

GET /api/v1/workspace-projects?limit&offset&search возвращает WorkspaceProjectPage с items/total. Limit 1–100; offset неотрицательный. До полного снимка — 503, подтверждённый пустой снимок — пустой список. Отдельный проект читается через GET /api/v1/workspace-projects/{registry}/{namespace}.

WorkspaceProject содержит NamespaceRef, Tracker ResourceRef, name/project_key, lifecycle/generation, observed_at/stale и optional Wiki binding/space_key. resource_status: connected, not_connected, forbidden, unavailable. Отказ доступа скрывает Wiki resource identity и key; project metadata не выдаёт права.

GET /api/v1/documents/{id}/namespace-context проверяет доступ к документу и возвращает подтверждённый ResourceContext либо null для standalone документа. Потерянная managed projection возвращает unavailable. Старые API сохраняются. Аналогичный GET /api/v1/spaces/{space_key}/namespace-context разрешает явные legacy Space links через UUID локального ресурса. Обычные standalone credentials не получают новый межпродуктовый каталог: доступны central profiles и admin; owner-local Namespace/document/space чтение сохраняет прежнюю проверку доступа.

1. Назначение

REST API Wiki предоставляет базовые операции продукта: auth, users, spaces, documents, revisions, task links, phase links, evidence, attachments, templates, settings, search и audit.

Текущий openapi/openapi.json зафиксирован под Wiki MVP API. Production server требует WIKI_DATABASE__URL и использует SQLx/PostgreSQL persistence; memory backend остаётся только явным test/dev router mode для быстрых API tests.

2. Общие правила

  • Base path: /api/v1.
  • UI и CLI используют один и тот же публичный API.
  • Все ответы JSON, кроме download endpoints.
  • Ошибки MVP возвращаются единым JSON envelope { "error": { "code": "CODE", "message": "message" } }; requestId и details добавляются как опциональные поля, когда доступны.
  • SQLx guards сохраняют403 для42501 и409 для40001/40P01; автоматического повтора записи нет. Диагностические сообщения БД не выдаются клиенту. Подробности.
  • Backend возвращает X-Request-ID на каждый ответ: echo валидного клиентского заголовка или новый req_ UUIDv7 для запроса без request id.
  • Большие списки MVP используют bounded limit и стабильную сортировку: revisions, evidence, search, audit.
  • Protected domain/admin POST/PUT/DELETE requests may send Idempotency-Key; auth/session endpoints are outside this replay scope. The server stores successful responses for 24 hours by (actor, key, method, path+query, request body hash) and replays the same response on retry. Reusing the same key for a different request, or retrying while the first request is still processing, returns 409 CONFLICT.
  • Protected endpoints требуют Central Auth Bearer token. При настроенном WIKI_AUTH__CENTRAL_JWKS_URI недоступность Central Auth возвращается как временная ошибка без fallback на локальный пароль.
  • API не раскрывает секреты, bearer tokens, private storage keys и stack traces.

3. Health and Readiness

Operational endpoints are part of the public API surface but do not create Wiki domain data and do not require a frontend route.

Method Path Назначение
GET /health Liveness check для процесса API
GET /health/ready Readiness check после инициализации runtime dependencies

4. Auth

В платформенном режиме UI использует Central Auth Authorization Code + PKCE. Локальные register/login/refresh endpoints остаются только для legacy/test режима и закрыты при настроенном Central Auth.

Method Path Назначение
POST /auth/register Регистрация пользователя; возвращает 403, если WIKI_AUTH__REGISTRATION_ENABLED=false
POST /auth/login Вход
POST /auth/refresh Обновление access token
POST /auth/logout Завершение сессии
GET /users/me Текущий пользователь

5. Users and Access

Method Path Назначение
GET /users Активный центральный каталог для назначения
POST /users Legacy only; 403 в центральном режиме
PUT /users/{user_id} Legacy only; 403 в центральном режиме
GET /spaces/{space_key}/members Участники space
PUT /spaces/{space_key}/members/{user_id} Назначить роль в space
DELETE /spaces/{space_key}/members/{user_id} Удалить участника из space

DELETE /spaces/{space_key}/members/{user_id} возвращает 404 NOT_FOUND, если такой membership уже отсутствует. Успешный 204 означает, что membership реально был удалён; обычный пользователь без другой роли больше не проходит проверки чтения/поиска для этого space.

Учётки, установка пароля, отключение и личные API-токены управляются в Admin Panel. Центральные люди получают одинаковые пользовательские права в Wiki; исторические роли сохраняются только для совместимости legacy-режима.

6. Spaces

Method Path Назначение
GET /spaces Список доступных spaces
POST /spaces Создать space
GET /spaces/{space_key} Получить space
PUT /spaces/{space_key} Обновить metadata
POST /spaces/{space_key}/archive Архивировать space
GET /spaces/{space_key}/tree Page tree

Архивированный space остаётся доступен на чтение и администрирование пользователям с правами, но content write-команды внутри него возвращают 400 VALIDATION_ERROR: создание/изменение/публикация/перемещение/архивирование документов, создание evidence и task/phase document links.

7. Documents

Method Path Назначение
POST /spaces/{space_key}/documents Создать документ
GET /documents/{document_id} Открыть документ
PUT /documents/{document_id}/draft Обновить draft
POST /documents/{document_id}/publish Опубликовать новую ревизию
POST /documents/{document_id}/archive Архивировать документ
POST /documents/{document_id}/move Переместить в дереве
GET /documents/{document_id}/revisions История ревизий
GET /documents/{document_id}/revisions/{revision_id} Конкретная ревизия

Архивированный документ остаётся доступен на чтение пользователям с правом доступа к space, но write-команды draft, publish, move, archive и task/phase document links возвращают 400 VALIDATION_ERROR.

POST /documents/{document_id}/publish принимает summary и необязательный base_revision_id. Если base_revision_id передан, API сравнивает его с текущей опубликованной ревизией документа внутри publish-транзакции; несовпадение возвращает 409 CONFLICT с сообщением document draft is based on a stale revision. Для первой публикации или legacy-клиентов поле можно не передавать.

Редактируемый DocumentResponse дополнительно содержит draft_checksum — SHA-256 заголовка и тела текущего черновика с однозначным разделением полей. UI и CLI передают его как expected_draft_checksum при публикации. Проверка выполняется под той же блокировкой, что и создание ревизии; изменение заголовка или тела возвращает 409 CONFLICT (document draft changed before publication), без публикации чужого черновика. UI сохраняет локальный ввод и также отказывается публиковать, если ответ сохранения уже содержит чужой текст или сервер не поддерживает checksum. Read-only пользователи checksum не получают. Поле запроса остаётся необязательным для совместимости: legacy API-клиенты без него по-прежнему публикуют текущий серверный черновик без проверки его точного содержимого.

Пустое тело разрешено сохранять как черновик. Редактор показывает его пустым, включая повторное открытие страницы; опубликованный текст его не заменяет. Публикация пустого тела по-прежнему отклоняется сервером и недоступна в UI.

GET /documents/{document_id}/revisions возвращает историю в порядке от последней опубликованной ревизии к первой. Endpoint bounded: без limit отдаёт последние 20 ревизий, limit ограничивается диапазоном 1..100; необязательный offset (по умолчанию 0) пропускает указанное число более новых ревизий. Для постраничного просмотра клиент запрашивает limit=21&offset=20*N, показывает первые 20 и использует последнюю запись как признак следующей страницы. Опубликованные ревизии immutable: новый draft или повторная публикация не меняют тело, заголовок и summary уже созданных ревизий.

DocumentResponse and DocumentRevisionResponse expose both body_markdown and body_html for published content. body_markdown is the canonical source for CLI export; body_html is the sanitized HTML rendered by the backend from the published revision and is the only HTML surface the UI should render. DocumentResponse.can_edit tells clients whether draft and write controls are available. draft_markdown is populated only when can_edit=true; read-only viewers receive an empty draft_markdown and only the published body fields.

Локальный предпросмотр нового черновика строит безопасные React-элементы из CommonMark через react-markdown с отключённым raw HTML. Это не новое API, не сохранение и не публикация: исходник остаётся в состоянии редактора.

8. Task Links

Task dossier в MVP - это представление документов/evidence, связанных одним внешним task_key. Wiki не владеет статусом задачи.

Method Path Назначение
GET /spaces/{space_key}/tasks Список task keys, известных Wiki
GET /spaces/{space_key}/task-summaries Компактный каталог task dossiers с cursor
GET /spaces/{space_key}/tasks/{task_key} Сводка по task key
POST /spaces/{space_key}/tasks/{task_key}/links/documents Привязать документ к task key
GET /spaces/{space_key}/tasks/{task_key}/documents Документы task key
GET /spaces/{space_key}/tasks/{task_key}/evidence Evidence task key

9. Phase Links

Phase dossier в MVP - это представление документов/evidence по phase_key. Wiki не управляет переходами фаз.

Method Path Назначение
GET /spaces/{space_key}/phases Список phase keys, известных Wiki
GET /spaces/{space_key}/phase-summaries Компактный каталог phase dossiers с cursor
GET /spaces/{space_key}/phases/{phase_key} Сводка по phase key
POST /spaces/{space_key}/phases/{phase_key}/links/documents Привязать документ к phase key
GET /spaces/{space_key}/phases/{phase_key}/documents Документы phase key
GET /spaces/{space_key}/phases/{phase_key}/evidence Evidence phase key

task-summaries и phase-summaries возвращают только space_key, ключ, title, document_count и evidence_count, без вложенных документов или evidence. limit по умолчанию 20, допускается 1..100; cursor — последний ключ предыдущей страницы, в ответе next_cursor=null означает конец. total — точное число совпадений во всём пространстве с учётом q, но без применения cursor; оно остаётся тем же даже на пустой странице после конца списка. Необязательный q ищет без учёта регистра по ключу, сохранённому названию и названиям связанных неархивных документов во всём пространстве до пагинации. Ключи сортируются в бинарном порядке, поэтому page boundary стабилен при совпадающих датах. Счётчик документов не включает архивные документы. Старые /tasks и /phases оставлены для совместимости, но новым каталогам следует использовать bounded summary endpoint.

10. Evidence and Attachments

Method Path Назначение
POST /evidence Создать URL/file evidence
GET /evidence Список evidence с фильтрами
GET /evidence/{evidence_id} Получить evidence
POST /attachments Загрузить файл
GET /attachments/{attachment_id} Metadata файла
GET /attachments/{attachment_id}/download Скачать файл

Canonical evidence_type values for MVP are external_url and uploaded_file. external_url accepts a non-empty url and must not include attachment_id or checksum; uploaded_file accepts attachment_id without url and stores checksum from the staged attachment. Evidence can be linked to document_id, task_key, phase_key or their combination inside one space. If document_id is present and space is omitted, API uses the document's space; if explicit space conflicts with the document's space, API returns 400 VALIDATION_ERROR. GET /evidence supports space, document_id, task_key, phase_key, q, cursor and limit; all filters and case-insensitive substring search (q, maximum 200 characters) run server-side within spaces visible to the caller. Results are ordered by (created_at DESC, id DESC). The response contains evidence and nullable next_cursor; pass the cursor back with the same filters to read the next page. An invalid cursor returns 400 VALIDATION_ERROR. Without limit, API returns the latest 30 evidence items; limit is clamped to 1..100. Specific source categories such as CI job, pull request, deployment or test artifact are metadata, not separate evidence types.

11. Search

Method Path Назначение
GET /search Поиск документов и материалов

Фильтры MVP: q, space, task_key, phase_key, document_type, result_type (document или evidence), include_archived, limit, cursor. Без limit поиск возвращает 20 результатов; limit ограничивается диапазоном 1..100. result_type применяется до ограничения страницы, поэтому вкладка материалов не зависит от числа более новых документов. Ответ содержит results и next_cursor (null на последней странице). Результаты отсортированы по (updated_at DESC, id DESC, result_type DESC); клиент передаёт непрозрачный next_cursor с теми же фильтрами для следующей страницы. Общее количество совпадений API не обещает. Ссылки на документы используют UUID, чтобы одинаковые slug в разных пространствах не вели к чужому документу.

Для опубликованных документов поиск использует текущую опубликованную ревизию. Если у опубликованного документа есть новый непубликованный draft, его текст не попадает в общий search response до следующей публикации.

12. Templates, Settings and Audit

Method Path Назначение
GET /templates Список шаблонов
POST /templates Создать шаблон, system admin only
GET /settings Admin-only read-only snapshot безопасных runtime настроек инстанса
GET /audit-log Страницы audit events для admin UI; limit clamps to 1..200, необязательные cursor, action, entity_type, actor_id, from, to

GET /settings не возвращает секреты, connection strings, storage paths или bootstrap credentials. MVP endpoint показывает только значения, нужные UI/CLI: API path, регистрацию, storage/search backend, лимит загрузки, язык и timezone.

GET /audit-log возвращает append-only события с request_id в порядке (created_at DESC, id DESC). Endpoint всегда bounded: без параметра отдаёт последние 50 событий, limit ограничивается диапазоном 1..200. Ответ содержит next_cursor (null на последней странице); для следующей страницы клиент передаёт его неизменённым в cursor. Необязательные action и entity_type сравниваются точно, actor_id принимает UUID, from и to принимают RFC3339 и задают полуоткрытый интервал [from, to) в UTC. Фильтры действуют на весь audit до cursor/limit; при переходе к следующей странице клиент сохраняет тот же набор фильтров. Неверные UUID/даты, обратный диапазон и неизвестный формат курсора дают 400. Keyset-пагинация сохраняет границы уже открытых страниц при появлении новых событий и различает записи с одинаковым временем. Для mutating HTTP-запросов audit entry использует тот же X-Request-ID, который backend вернул клиенту в response header; если запрос пришёл без валидного id, middleware создаёт req_ id и он попадает в audit.

13. Deferred API Areas

  • Comments.
  • Reports.
  • Notifications.
  • Integrations and source-specific tokens.
  • Approval chains.
  • Import/export bundles.
  • Webhook ingestion or delivery.
  • Runner/worker control protocols.

14. Contract Freeze For Main Development

The pre-development API contract is frozen when these checks pass:

  • every runtime /api/v1 route appears in openapi/openapi.json;
  • every OpenAPI path appears in this document and in docs/PRODUCT_REQUIREMENTS.md;
  • frontend generated DTOs compile against the committed OpenAPI artifact;
  • UI and CLI call only public API endpoints, never PostgreSQL or storage internals;
  • The Prometheus metrics endpoint is documented in operations but is not part of the versioned /api/v1 OpenAPI contract.

15. Required Negative Cases

Area Required behavior
Auth Bad credentials return an auth error; disabled registration returns 403; missing/invalid bearer tokens return the standard error envelope; refresh rotates access and refresh token paths; logout and user deactivation invalidate existing token paths.
Access No role or removed membership blocks document, tree, evidence, attachment and search reads for that space; viewer document reads do not expose unpublished drafts.
Spaces Archived spaces reject document, evidence and task/phase link write commands while keeping read/admin visibility.
Documents Archived documents reject draft, publish, move, archive and task/phase link writes; duplicate slugs return conflict; stale base_revision_id returns conflict; cyclic moves return validation error.
Evidence Evidence must target at least one document/task/phase; explicit space cannot conflict with document space.
Attachments Empty upload, unsafe filename, oversized body and unauthorized download are rejected.
Search Results are bounded, permission-filtered and do not expose unpublished draft text.
Settings Secrets, connection strings, storage paths and bootstrap credentials are never returned.
Health /health/ready fails until runtime dependencies are initialized.
Idempotency Repeating a successful protected domain/admin write with the same Idempotency-Key replays the original response without a duplicate write; reusing the key for another payload returns 409 CONFLICT.

Multipart idempotency в CLI-пакете

Replay защищённых upload requests с одинаковым key сравнивает упорядоченные поля multipart: имя поля, filename, content type и checksum содержимого. Случайный boundary не влияет на hash. Изменение файла или его метаданных с прежним key даёт conflict. JSON writes сохраняют прежний request hash и защиту base revision. Upload keys, сохранённые предыдущей версией по raw multipart body, сохраняют старый hash до истечения retention; replay через новую версию может вернуть conflict, поэтому такие ключи нельзя использовать для автоматического повторного создания upload.

Общая база

Подключение версий, границы контрактов и проверки описаны в BASE_INTEGRATION.

Сквозной Namespace

Версионированные API, данные, ownership и совместимость описаны в Namespace.

Проекты пространства

  • GET /api/v1/spaces/{space_key}/projects → SpaceProjectsResponse: собственное пространство (id/key/name/status/resource_status) и коллекция проектов (namespace/name/project_key/state/available). Требуется Space View; локальному пользователю без доступа к внешнему каталогу не выдаются имена проектов.
  • PUT /api/v1/spaces/{space_key}/projects/{registry}/{namespace} → 204: добавить связь. Space Admin и право каталога; новый проект должен быть известным, active и подтверждённым свежим снимком. Повтор не создаёт записи/аудит.
  • DELETE по тому же пути → 204: удалить только связь. Space Admin; операция сохраняет документы и работает при недоступных метаданных проекта.
  • GET /api/v1/documents/{id}/space-context → SpaceProjectsResponse: пространство документа и его проекты после проверки доступа.

WorkspaceProject.spaces — авторитетная коллекция доступных пространств проекта. Недоступные по ACL пространства не раскрывают identity, name или key. Optional binding/space_key и старые namespace-context endpoints описывают первичный provisioning receipt и не используются новым UI для выбора пространства.

available-tasks принимает пару registry_instance_id/namespace_id в query; LinkTaskRevision принимает optional namespace. Отсутствие пары допустимо только при единственной связи пространства; несколько/ноль связей дают 409 select_space_project. Неполная query-пара — 400, несвязанный проект — 400. Связь задачи с ревизией сохраняет actual NamespaceRef после отвязки.

Каталог задач и создание новой верифицированной TaskRef-связи требуют Space Edit и отдельного доступа к проектному каталогу. Локальная роль в Wiki не выдаёт доступ к данным Tracker. Чтение исторических ссылок сохраняет Space View gate.

Глобальные роли и приложения

Исходная интеграция и открытые gates описаны в контракте ролей и приложений. Публикация исходников не означает активацию новой политики.

Общее пространство: source API v2

Строгие прежние schemas не расширяются. Отдельные новые contracts: GET /api/v2/spaces/{space_key}/projects — SpaceProjectsV2 с original actor, SpaceRef/ProjectRef и canonical Tracker pagination; PUT /api/v2/spaces/{registry}/{space}/owner-state — зарегистрированный Admin owner, SpaceOwnerCommandV2/readback без обязательного проекта; PUT /api/v2/projects/{tracker}/{project}/spaces/projection — зарегистрированный Tracker, original operation/revision/ACK. Сценарии, отказ legacy и границы. OpenAPI генерируется из Rust; установка требует полного совместимого cohort.

Обычное чтение документов и task/phase dossier не открывает draft-only metadata. Без подтверждённой роли редактора title/status берутся из опубликованной версии; search/count/page фильтруются до pagination. Прямой draft-only read —404. Read PAT редактора сохраняет чтение черновика без can_edit/права записи.

Freshness of displayed human data

Authenticated GET /api/v1/space-admission returns the shared UI display descriptor (schema_version1, enforced, remaining_millis, scope_revision) from the original WikiClaims admission. It never grants a subsequent operation. The client anchors the remaining clock before I/O, closes retired generations and clears caches and forms on source/role changes. An unavailable renewal cannot extend the old clock. Activated storage still checks each resource's actual owning namespace.

Private identity lifecycle API

Wiki реализует private owner protocol через общий storage port; полный inventory, неизменяемая issuer/alias привязка, правила shared/exclusive lock и квалификация описаны в Identity deletion. Наличие source не означает установленный или квалифицированный runtime. UI и прежний admission не меняются.

Служебное чтение материалов аналитиком

На 2026-11-05 разрешённое чтение Wiki проверено прямым CLI и оригинальным Hermes в отдельной PDLC1 QA: связь с проектом, поиск, опубликованная редакция, история, точная версия и вложение. Подтверждены пустая выдача, ошибка сервиса, отказ чужому пространству и отзыв/восстановление viewer membership теми же credentials. Версии, команды и доказательства — в сводке Base. Workflow, Fleet и изменение установленного окружения в эту приёмку не входят.

WIKI_READERS__SUBJECTS явно регистрирует отдельные Central Auth service accounts. Их PAT имеет только wiki:read, без browser session; owner/projection identities не могут использовать этот доступ. Профиль заранее подготавливается операторской командой, viewer membership выдаётся обычным API только нужных пространств проекта. Чтение не создаёт профиль и не выдаёт membership. Удаление membership, блокировка профиля либо отзыв PAT закрывают последующие запросы.

Официальный CLI использует прежние GET: spaces/tree/projects, documents/revisions, task documents/evidence, evidence/attachments/download и search. Классификация идёт по маршруту сервера; остальные GET и все mutations для reader запрещены. Машина сохраняет собственную identity и не получает human/admin context. Даже историческая admin/editor роль не открывает write или private draft. В режиме общего admission чтение ограничено одновременно текущей политикой Space и явными memberships; политика All сама по себе не открывает другое пространство. Источник и срок proof остаются исходными, body/download ограничены существующим response guard.

Docker/Compose и Kubernetes передают один и тот же WIKI_READERS__SUBJECTS и отдельный read PAT через штатную конфигурацию. Изменение источников не активирует этот доступ на существующей установке; отсутствующая регистрация сохраняет запрет.

Общий /auth/directory содержит людей, поэтому machine-профиль подготавливается отдельно после обычного применения миграций: wiki-server prepare-reader <subject-uuid> --token-file <private-read-pat-file>. Команда запускается оператором с обычной Wiki-конфигурацией БД/Auth и регистрацией subject. Она проверяет именно этот service account и его exact wiki:read PAT через Auth, использует существующую проекцию профиля, печатает только local UUID и не выдаёт membership. Повтор сохраняет UUID и локальную блокировку; совпадение email с другим subject не привязывает профиль. Файл PAT принадлежит Secret/configuration среды и не передаётся агенту как admin credential. Команда одинакова для Compose и Kubernetes; сервер без аргументов запускается как прежде.

CLI wiki space project-contexts KEY --limit 50 --offset 0 явно читает существующий canonical /api/v2/spaces/{space_key}/projects; API base оканчивается на /api/v1. Прежний space projects сохраняет legacy V1. Команда не меняет режим автоматически после 403/503 и не обходит общий admission.