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 чтение сохраняет прежнюю проверку доступа.
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.
- 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/DELETErequests may sendIdempotency-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, returns409 CONFLICT. - Protected endpoints требуют Central Auth Bearer token. При настроенном
WIKI_AUTH__CENTRAL_JWKS_URIнедоступность Central Auth возвращается как временная ошибка без fallback на локальный пароль. - API не раскрывает секреты, bearer tokens, private storage keys и stack traces.
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 |
В платформенном режиме 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 |
Текущий пользователь |
| 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-режима.
| 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.
| 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,
не сохранение и не публикация: исходник остаётся в состоянии редактора.
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 |
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.
| 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.
| 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 до следующей публикации.
| 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.
- Comments.
- Reports.
- Notifications.
- Integrations and source-specific tokens.
- Approval chains.
- Import/export bundles.
- Webhook ingestion or delivery.
- Runner/worker control protocols.
The pre-development API contract is frozen when these checks pass:
- every runtime
/api/v1route appears inopenapi/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/v1OpenAPI contract.
| 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. |
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.
Версионированные 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 описаны в контракте ролей и приложений. Публикация исходников не означает активацию новой политики.
Строгие прежние 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/права записи.
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.
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.