openapi: 3.0.0
paths:
  /.well-known/jwks.json:
    get:
      description: Один ключ в обычном режиме; два — во время ротации AUTH_JWT_SECRET.
      operationId: JwksController_jwks
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JwksResponseDto'
      summary: Публичные ключи проверки access-токена
      tags:
        - auth
  /auth/login:
    post:
      description: >-
        Несуществующий email, неверный пароль и заблокированная учётная запись
        возвращают ровно один и тот же ответ 401 — то же тело, тот же статус.
      operationId: AuthController_login_
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessaoResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Неверные учётные данные.
        '403':
          description: Email ещё не подтверждён.
      summary: Аутентифицирует и выдаёт пару access + refresh
      tags:
        - auth
  /auth/logout:
    post:
      description: >-
        Всегда 204, даже для неизвестного токена — ответ 401 здесь стал бы
        оракулом валидности токена.
      operationId: AuthController_logout_
      parameters: []
      responses:
        '204':
          description: Сессия завершена. Cookie сессии очищены.
      summary: Отзывает семейство предъявленного refresh
      tags:
        - auth
  /auth/oauth/{provider}/callback:
    get:
      description: >-
        Успех ведёт на `WEB_ORIGIN/` с уже установленными cookie сессии; неудача
        — на `WEB_ORIGIN/login?oauth_error=1`, без указания причины.
      operationId: AuthController_oauthCallback
      parameters:
        - name: provider
          required: true
          in: path
          schema:
            enum:
              - github
              - gitlab
            type: string
        - name: code
          required: true
          in: query
          schema:
            type: string
        - name: state
          required: true
          in: query
          schema:
            type: string
      responses:
        '302':
          description: Перенаправляет в веб-приложение.
          headers:
            Location:
              description: Адрес назначения в веб-приложении.
              schema:
                type: string
                example: http://localhost:5173/
      summary: Принимает OAuth-callback социального входа
      tags:
        - auth
  /auth/oauth/{provider}/start:
    get:
      description: >-
        `state` подписывается HMAC со СВОИМ назначением — никогда не с
        назначением потока подключения git к проекту (см. ADR 0084).
      operationId: AuthController_oauthStart
      parameters:
        - name: provider
          required: true
          in: path
          schema:
            enum:
              - github
              - gitlab
            type: string
      responses:
        '302':
          description: Перенаправляет к провайдеру.
          headers:
            Location:
              description: URL авторизации провайдера.
              schema:
                type: string
                example: https://github.com/login/oauth/authorize?…
        '400':
          description: Провайдер вне `github`/`gitlab`.
      summary: Перенаправляет к OAuth-провайдеру для социального входа
      tags:
        - auth
  /auth/refresh:
    post:
      description: >-
        Предъявленный refresh расходуется. Повторное предъявление уже
        ротированного токена отзывает ВСЁ семейство и записывает событие
        безопасности — даже если причиной была двойная отправка клиентом.
      operationId: AuthController_refresh_
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessaoResponseDto'
        '401':
          description: Refresh недействителен, истёк или уже использован.
      summary: Ротирует refresh и выдаёт новую пару
      tags:
        - auth
  /auth/register:
    post:
      description: >-
        Отвечает 202 и для нового адреса, и для уже зарегистрированного — ответ
        не раскрывает, существует ли учётная запись. Во втором случае ничего не
        создаётся, а владелец адреса получает уведомление.
      operationId: AuthController_register
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegisterDto'
      responses:
        '202':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AceiteResponseDto'
        '400':
          description: Пароль не соответствует минимальной политике.
        '403':
          description: Регистрация закрыта (AUTH_REGISTRATION_ENABLED=false).
      summary: Создаёт учётную запись и отправляет письмо для подтверждения
      tags:
        - auth
  /auth/request-password-reset:
    post:
      description: >-
        Отвечает 202 и для известного, и для неизвестного адреса. Это также путь
        для тех, кто был импортирован из Keycloak и ещё не задал пароль.
      operationId: AuthController_requestPasswordReset
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RequestPasswordResetDto'
      responses:
        '202':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AceiteResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
      summary: Запрашивает ссылку для сброса пароля
      tags:
        - auth
  /auth/reset-password:
    post:
      description: >-
        Отзывает ВСЕ сессии пользователя. Не выдаёт токены: тот, кто сбрасывает
        пароль, отправляется на вход, чтобы компрометация email не означала
        захват учётной записи за один шаг.
      operationId: AuthController_resetPassword
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ResetPasswordDto'
      responses:
        '204':
          description: >-
            Пароль установлен. ВСЕ активные сессии пользователя отзываются
            вместе с этим.
        '400':
          description: Ссылка недействительна/истекла или пароль не соответствует политике.
      summary: Устанавливает новый пароль по токену
      tags:
        - auth
  /auth/verify-email:
    post:
      operationId: AuthController_verifyEmail
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VerifyEmailDto'
      responses:
        '204':
          description: Email подтверждён; вход уже работает.
        '400':
          description: >-
            Ссылка недействительна, истекла или уже использована — во всех трёх
            случаях один и тот же ответ.
      summary: Подтверждает email одноразовым токеном
      tags:
        - auth
  /gates:
    get:
      description: >-
        Декларативный индекс gate потока (ADR 0054). Экран берёт отсюда, какие
        шаги существуют и кто оценивает каждый, вместо того чтобы повторять
        список в коде — именно так он раньше незаметно устаревал.
      operationId: GatesController_listar
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GatesResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Реестр активных gate
      tags:
        - gates
  /git/oauth/{provider}/callback:
    get:
      description: >-
        Публичный, потому что приходит БРАУЗЕР пользователя от провайдера, без
        сессии api. Это не значит без ограничений: `state` проверяется HMAC, и
        без валидного вызов отклоняется. Никогда не отвечает JSON — всегда
        перенаправляет, потому что сырое тело ошибки при навигации в браузере
        было бы ужасным опытом.
      operationId: GitController_callback
      parameters:
        - name: provider
          required: true
          in: path
          schema:
            enum:
              - github
              - gitlab
            type: string
        - name: code
          required: true
          in: query
          schema:
            type: string
        - name: state
          required: true
          in: query
          schema:
            type: string
      responses:
        '302':
          description: >-
            Успех ведёт на `WEB_ORIGIN/projects/:id?git=connected`; неудача — на
            `WEB_ORIGIN/git-error`. Ошибка НЕ раскрывает причину в URL.
          headers:
            Location:
              description: Адрес назначения в веб-приложении.
              schema:
                type: string
                example: http://localhost:5173/projects/01JC…?git=connected
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует.
      summary: Принимает возврат OAuth и перенаправляет в веб-приложение
      tags:
        - git
  /health:
    get:
      description: >-
        Выполняет `select 1`. Сообщает только, отвечает ли база данных; ни
        топологии, ни версии. Публичный, потому что вызывающий — kubelet, у
        которого нет токена.
      operationId: HealthController_check
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthStatusResponseDto'
        '503':
          description: База данных не ответила. Тело содержит сообщение в `details`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthStatusResponseDto'
      summary: Readiness — процесс действительно может обслуживать трафик
      tags:
        - infrastructure
  /internal/first-account:
    post:
      description: >-
        Для установщика в одну строку, который работает там, где почтовый
        транспорт не настроен: сгенерированный `.env` не содержит почтовых
        переменных, `MAIL_TRANSPORT` падает в `log`, и обычная регистрация ждёт
        письма, которое никогда не придёт. Учётная запись рождается
        подтверждённой, потому что тот, кто запускает установщик, уже доказал
        нечто БОЛЕЕ СИЛЬНОЕ, чем контроль над почтовым ящиком, — он контролирует
        машину, `.env` и демон Docker. Обычная регистрация не затрагивается.
        Отклоняет с 409, если в установке есть ЛЮБОЙ пользователь: условие
        касается установки, а не этого email, поэтому восстановление при
        миграции глушит шаг той же проверкой. Пароль здесь никогда не
        генерируется и нигде не хранится, кроме как в виде хеша argon2id; личное
        рабочее пространство (RN-410) рождается в той же транзакции.
      operationId: InternalFirstAccountController_criar
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FirstAccountInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FirstAccountInternalResponseDto'
        '400':
          description: >-
            Некорректный email или пароль, отклонённый политикой ДОМЕНА, — тот
            же код, что вызывает регистрация, никогда не второе правило.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '409':
          description: В установке уже есть пользователь. Ничего не создано.
      security:
        - service-token: []
      summary: Создаёт ПЕРВУЮ учётную запись установки, уже подтверждённую
      tags:
        - internal
  /internal/gates:
    get:
      description: >-
        Реестр ОПИСЫВАЕТ, а не исполняет: ни один gate не становится
        обязательным из-за него, и изменение поля здесь не меняет никакого
        поведения. Каждый gate говорит, где находится доказательство его
        прохождения (`evidencia`), потому что не каждое доказательство есть в
        журнале событий.
      operationId: InternalGatesController_ler
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GateRegistryResponseDto'
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
      security:
        - service-token: []
      summary: Читает декларативный реестр gate
      tags:
        - internal
  /internal/graph/prompt-templates:
    post:
      description: >-
        Если для того же `name` уже существует версия с ТЕМ ЖЕ хешем, новая
        версия не создаётся — возвращается существующая (попадание
        идемпотентности не меняет HTTP-статус: всегда 201, для простоты — тело
        идентично тому, что было бы создано).
      operationId: InternalGraphController_upsert
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpsertPromptTemplateInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PromptTemplateResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '503':
          description: >-
            Neo4j не настроен или недоступен — никакой запасной вариант для
            чтения/записи шаблона невозможен.
      security:
        - service-token: []
      summary: Публикует версию шаблона промпта, идемпотентно по хешу
      tags:
        - internal
  /internal/graph/prompt-templates/{name}:
    get:
      description: >-
        Конкретная `version` ищется по равенству; если опущена, ищется самая
        свежая версия `active`. 404, если шаблон (или запрошенная версия) не
        существует.
      operationId: InternalGraphController_getByName
      parameters:
        - name: name
          required: true
          in: path
          schema:
            type: string
        - name: version
          required: false
          in: query
          schema:
            example: '3'
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PromptTemplateReadResponseDto'
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Шаблон не существует или не имеет запрошенной версии.
        '503':
          description: >-
            Neo4j не настроен или недоступен — никакой запасной вариант для
            чтения/записи шаблона невозможен.
      security:
        - service-token: []
      summary: Ищет шаблон промпта по имени
      tags:
        - internal
  /internal/machine-device-keys:
    post:
      description: >-
        Для установщика в одну строку, сразу после создания первой учётной
        записи: локальному агенту нужны учётные данные, а единственные
        существовавшие были привязаны к ПРОЕКТУ — в установке, где проектов ещё
        нет. Пара генерируется НА МАШИНЕ, и сюда приходит только публичная
        половина; запишите возвращённый `id` в приватный JWK как `kid` (RN-475)
        — это единственная связь между файлом на диске и публичной половиной на
        сервере. `userId` в теле НЕТ НАМЕРЕННО: владелец — ЕДИНСТВЕННЫЙ
        пользователь установки, определяемый api, поэтому владение сервисным
        токеном никогда не означает выбор, чьи учётные данные выпустить.
        Регистрация ЗАМЕНЯЕТ: активные машинные ключи владельца отзываются в той
        же транзакции, так что переустановленная машина работает, а тысяча
        машинных ключей существовать не может. Ключи проекта (ADR 0118) никогда
        не затрагиваются.
      operationId: InternalMachineDeviceKeysController_registrarChave
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MachineDeviceKeyInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MachineDeviceKeyInternalResponseDto'
        '400':
          description: >-
            JWK не является ПУБЛИЧНЫМ ключом Ed25519 — некорректный JSON,
            неверный `kty`/`crv`, отсутствует `x` или присутствует `d` (это
            приватная половина, и она отклоняется по имени, а не сохраняется).
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '409':
          description: >-
            В установке нет пользователя или их больше одного. Ничего не
            записано. Этот маршрут относится к моменту установки и навсегда
            замолкает, когда у установки появляется команда.
      security:
        - service-token: []
      summary: Регистрирует МАШИННЫЙ ключ устройства новой установки
      tags:
        - internal
  /internal/models/sync:
    post:
      description: >-
        Никогда не падает из-за одного провайдера: каждый отвечает за себя в
        отчёте, с причиной пропуска и источником сбоя, если он был. Провайдер,
        который не ответил, ПРОПУСКАЕТСЯ, а не помечается недоступным.
      operationId: InternalModelsController_sync
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SyncModelCatalogResponseDto'
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
      security:
        - service-token: []
      summary: Синхронизирует каталог моделей по всем провайдерам
      tags:
        - internal
  /internal/projects/{projectId}/backlog:
    get:
      description: >-
        ТО ЖЕ дерево эпик → история → задача, что и во вкладке Backlog, через
        тот же сценарий использования (три чтения на проект, никогда N+1).
        Именно с ним PO видит эпик без историй и историю без задач, вместо того
        чтобы заново создавать уже существующее (RN-164).
      operationId: InternalProjectsController_backlog
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/EpicComHistoriasResponseDto'
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
      security:
        - service-token: []
      summary: Backlog проекта в виде дерева, чтобы PO читал уже написанное
      tags:
        - internal
  /internal/projects/{projectId}/business-rules:
    get:
      description: >-
        Все `artifact.business_rule` из сессий проекта — не только текущей
        сессии, что было потолком контекста kickoff у PO, — с полным
        `description` и тем, какие истории уже ссылаются на каждое правило.
        `uncoveredCount` — это незакрытое: правило, которое не покрывает ни одна
        история (RN-164). Проект совсем без правил отвечает `200` с пустым
        списком: «ни одного правила ещё не собрано» — не ошибка.
      operationId: InternalProjectsController_businessRules
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectBusinessRulesResponseDto'
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
      security:
        - service-token: []
      summary: Бизнес-правила проекта с покрытием, для чтения PO
      tags:
        - internal
  /internal/projects/{projectId}/container-exec:
    post:
      description: >-
        Вызывается только engine, когда `Engine.Actions.TerminalExecutor` решил,
        что команда должна выполняться внутри настоящего контейнера
        (`execution_mode: container` с ЗАРЕГИСТРИРОВАННОЙ строкой `running`).
        Проксирует в `ContainerBrokerPort.exec` (ADR 0130/0134) — последняя из
        пяти операций broker, получившая реального вызывающего. `sucesso: false`
        в ответе — никогда не выброшенная ошибка — это НОРМАЛЬНАЯ форма для
        отказавшего или недоступного broker: контейнер мог умереть или быть
        удалён извне между зарегистрированной строкой `running` и этим вызовом
        (RN-486, зарегистрированное и наблюдаемое никогда не сливаются), и это
        сбой команды, а не ошибка транспорта.
      operationId: InternalProjectsController_containerExec
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContainerExecInternalDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContainerExecInternalResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
      security:
        - service-token: []
      summary: >-
        Выполняет команду терминала ВНУТРИ настоящего контейнера проекта
        (RN-492)
      tags:
        - internal
  /internal/projects/{projectId}/container-spec:
    get:
      description: >-
        Идентичность проекта, режим выполнения и текущее решение Архитектора об
        образе. Broker перепроверяет всё это, прежде чем что-либо передать
        демону, — читать отсюда не то же самое, что доверять, и отказ называет
        поле. АБСОЛЮТНЫЙ путь не возвращается: источник bind разрешается демоном
        относительно файловой системы ХОСТА, и путь изнутри контейнера api молча
        смонтировал бы пустую папку. Передаётся `localizacao` (RN-503) — какой
        из двух корней broker использовать плюс относительный сегмент, который
        этот корень не покрывает.
      operationId: InternalContainersController_containerSpec
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: papel
          required: false
          in: query
          schema:
            enum:
              - qa
            type: string
        - name: task
          required: false
          in: query
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContainerSpecInternalResponseDto'
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Проект не найден.
      security:
        - service-token: []
      summary: Что нужно broker, чтобы самому составить спецификацию контейнера
      tags:
        - internal
  /internal/projects/{projectId}/git-remote:
    get:
      description: >-
        Возвращает чистый origin (без встроенных учётных данных) и, для
        удалённого провайдера, токен ВЛАДЕЛЬЦА рабочего пространства (RN-058),
        расшифрованный на лету. Потребитель внедряет токен при каждом вызове и
        НИКОГДА не записывает его в файл — `.git/config` находится внутри папки,
        где RN-075 даёт dev agent автоматически одобренный доступ на чтение.
      operationId: InternalProjectsController_gitRemote
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectGitRemoteResponseDto'
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: >-
            У проекта нет созданного репозитория, или у владельца рабочего
            пространства нет зарегистрированных учётных данных для провайдера
            репозитория.
      security:
        - service-token: []
      summary: Рабочий remote проекта для fetch и push
      tags:
        - internal
  /internal/projects/{projectId}/mirror-sync-result:
    post:
      description: >-
        Вызывается только engine, после того как runner отправляет
        `mirror_sync_result` по каналу, — ТОТ ЖЕ путь, что и у
        `workspace_confirm` (runner → канал → engine → api), никогда не второй
        механизм: engine сообщает, а не пишет в таблицу. Это ТЕЛЕМЕТРИЯ, а не
        доменное событие: никогда не `session_events` (у раунда зеркала нет
        сессии, а `session_id` — `NOT NULL`, то же рассуждение, что сделало
        `rag_searches` таблицей) и никогда не `proposed_action` (запись зеркала
        — это конфигурация, объявленная пользователем, а не агент, просящий
        действовать). Исход определяет `ok` — никогда не наличие счётчика или
        сообщения: раунд, скопировавший 0 файлов, нормален и не должен быть
        неотличим от раунда, который не запускался. Запись никогда не ломает то,
        что измеряет: копирование уже завершено, когда это вызывается, и engine
        только логирует отказ здесь. Проект, у которого тем временем очистили
        назначение, ВСЁ РАВНО записывает — раунд состоялся, и его ошибка обычно
        и объясняет, что пошло не так.
      operationId: InternalProjectsController_mirrorSyncResult
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MirrorSyncResultInternalDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MirrorSyncResultResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Проект не существует.
      security:
        - service-token: []
      summary: Локальный агент сообщает исход раунда зеркала (RN-517)
      tags:
        - internal
  /internal/projects/{projectId}/module-contracts:
    get:
      description: >-
        Одна запись на модуль ТЕКУЩЕГО module_map: что он потребляет
        (`dependeDe`, `dependsOn` карты) и что предоставляет (`expoe`, из
        текущего `artifact.module_contracts` Архитектора, или `null`, если
        ничего не объявлено). Это то, что читает dev agent вместо того, чтобы
        открывать worktree другого модуля (ADR 0200). Проект без контракта
        отвечает `200` со `status: sem_contratos`.
      operationId: InternalProjectsController_moduleContracts
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContratosDoProjetoResponseDto'
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
      security:
        - service-token: []
      summary: Контракты между модулями, для чтения dev agent (RN-684)
      tags:
        - internal
  /internal/projects/{projectId}/product-metrics:
    get:
      description: >-
        ТОТ ЖЕ отчёт, что и скрипт `analise:funil` (ADR 0089), — воронка сессия
        → коммит → PR → merge, реальное время выполнения и реальная частота
        развёртываний — через те же чистые функции и тот же запрос
        (`apps/api/src/application/services/funil-metrics.ts`), так что они
        никогда не расходятся для одного и того же факта. Закрывает
        `docs/fluxo.yml` (роль `po`, вход `metricas-de-produto`, ранее `status:
        lacuna`) (RN-407).
      operationId: InternalProjectsController_productMetrics
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductMetricsResponseDto'
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Проект не существует.
      security:
        - service-token: []
      summary: Воронка поставки проекта и частичные метрики DORA, для чтения PO
      tags:
        - internal
  /internal/projects/{projectId}/qa-environments/{taskId}/remove:
    post:
      description: >-
        Automatic, no proposal: it only reaches what carries the QA label (the
        broker guarantees it). Idempotent.
      operationId: InternalQaEnvironmentsController_remove
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: taskId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/QaEnvironmentResponseDto'
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Project or task not found.
      security:
        - service-token: []
      summary: Removes the QA instance of a task (RN-936)
      tags:
        - internal
  /internal/projects/{projectId}/qa-environments/{taskId}/start:
    post:
      description: >-
        Records the row, then asks the broker. `sucesso: false` is the NORMAL
        shape of a refused broker. At most ONE active instance per project:
        another task active -> 409 `instancia_de_qa_ocupada`. `runner` projects
        -> 409 `qa_nao_suporta_runner`.
      operationId: InternalQaEnvironmentsController_start
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: taskId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/QaEnvironmentResponseDto'
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Project or task (of that project) not found.
        '409':
          description: Busy instance, runner project or ambiguous task key.
      security:
        - service-token: []
      summary: Brings up the ephemeral QA instance of a task (RN-935)
      tags:
        - internal
  /internal/projects/{projectId}/workspace-verification:
    post:
      description: >-
        Вызывается только engine, после того как runner подключается и
        отправляет `workspace_confirm` по каналу. Runner — ИСТОЧНИК ИСТИНЫ для
        пути: api перезаписывает `workspacePath` тем, что он сообщил, после
        повторной лексической проверки (системный корень/пересечение с Brabo
        запрещены, даже если приходят от runner). Идемпотентно: повторное
        подключение с ТЕМ ЖЕ путём ничего заново не записывает.
      operationId: InternalProjectsController_confirmWorkspaceVerification
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ConfirmProjectWorkspaceInternalDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConfirmProjectWorkspaceResponseDto'
        '400':
          description: Лексически недопустимый путь, или проект не в режиме "runner".
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Проект не существует.
      security:
        - service-token: []
      summary: Runner подтверждает путь проекта "runner" (RN-423)
      tags:
        - internal
  /internal/qa-environments/sweep:
    post:
      description: >-
        The engine sends the task ids it still has in flight; every other active
        or failed instance is removed. Shared services and the QA network have
        no broker operation yet (declared gap).
      operationId: InternalQaEnvironmentsController_sweep
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SweepQaEnvironmentsInternalDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SweepQaEnvironmentsResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
      security:
        - service-token: []
      summary: Removes QA instances whose gate is no longer in flight (RN-937)
      tags:
        - internal
  /internal/rag/feedback:
    post:
      description: >-
        Тот же сценарий использования, что и у маршрута для человека, — никакого
        второго пути оценки. Неизвестный `searchId`/`chunkId` — это 400, который
        engine превращает в tool-result с ошибкой, чтобы модель исправилась
        (RN-061), но никогда не в падение.
      operationId: InternalRagController_votar
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RagFeedbackInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RagFeedbackInternalResponseDto'
        '400':
          description: >-
            Неизвестный `searchId` для этого проекта или `chunkId`, которого не
            было среди результатов этого поиска.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
      security:
        - service-token: []
      summary: Голос агента за один найденный фрагмент (RN-480)
      tags:
        - internal
  /internal/rag/search:
    post:
      description: >-
        Тот же механизм, что и `POST /projects/:projectId/rag/search` (маршрут
        для человека), в формате, который ожидает инструмент `rag_search`
        engine.
      operationId: InternalRagController_buscar
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RagSearchInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RagSearchInternalResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
      security:
        - service-token: []
      summary: Гибридный поиск по RAG-индексу проекта для инструмента engine
      tags:
        - internal
  /internal/sessions/{sessionId}/actions:
    post:
      description: >-
        ЕДИНСТВЕННАЯ дверь, через которую агент касается git, терминала или
        расходов. `permissions.json` решает при создании, и `deny` побеждает
        любую автономию, предоставленную агенту.
      operationId: InternalSessionsController_createAction
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateActionInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProposedActionResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Предлагает действие с внешним эффектом от имени агента
      tags:
        - internal
  /internal/sessions/{sessionId}/anamnese-context:
    get:
      description: >-
        Всё за один вызов, по образцу контекста Психолога. Участники приходят
        уже ИСКЛЮЧАЯ тех, кто отказался от профилирования, а решения
        пользователя в окне приходят отсюда, потому что их нет в журнале
        событий. Сами события engine читает прямо из Postgres — передавать их по
        HTTP было бы дороже, но не правильнее.
      operationId: InternalSessionsController_anamneseContext
      parameters:
        - name: projectId
          required: true
          in: query
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AnamneseContextResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Собирает контекст для раунда Анамнеза
      tags:
        - internal
  /internal/sessions/{sessionId}/anamnese-window:
    post:
      description: >-
        Записывает запуск анамнеза с нулём профилей, чтобы следующий раунд
        начинался с windowTo, — без этого окно никогда не сдвигалось, и каждый
        тик платил за новый раунд по тому же материалу.
      operationId: InternalSessionsController_anamneseWindow
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CloseAnamneseWindowInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecordProficiencyResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Закрывает окно Анамнеза раунда, не выпустившего ни одного профиля
      tags:
        - internal
  /internal/sessions/{sessionId}/c4-diagram:
    post:
      description: >-
        Артефакт — ЭТО событие `artifact.c4_diagram`: неизменяемое,
        версионированное и с автором, рядом с `artifact.module_map`. Требует
        текущего module_map — без него нечего рисовать на уровне Container
        (400).
      operationId: InternalSessionsController_c4Diagram
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateC4DiagramInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/C4DiagramaGeradoResponseDto'
        '400':
          description: >-
            Отсутствует `system_name`, недопустимый актор или нет текущего
            module_map.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Генерирует новую версию диаграммы C4 (Context + Container)
      tags:
        - internal
  /internal/sessions/{sessionId}/delegations:
    post:
      description: >-
        `completed` (с вердиктом), `failed` (с источником) или `dispensed` (с
        обоснованием) — lead никогда не вызывает этот маршрут с промежуточным
        исходом: каждое делегирование рождается здесь уже разрешённым.
      operationId: InternalSessionsController_recordDelegationOutcome
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecordDelegationInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DelegationResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Записывает исход делегирования в области
      tags:
        - internal
  /internal/sessions/{sessionId}/dev-context:
    get:
      description: >-
        Один вызов со всем, что нужно промпту: вся история, разрешённые
        бизнес-правила и применимые ADR. `module` ограничивает ADR модулем этого
        dev; ADR без объявленного модуля СКВОЗНОЙ и приходит всегда. Если
        `module` опустить, приходит вся коллекция, что и нужно gate QA и SecOps
        при повторном использовании этого контекста.
      operationId: InternalSessionsController_devContext
      parameters:
        - name: projectId
          required: true
          in: query
          schema:
            type: string
        - name: taskId
          required: true
          in: query
          schema:
            type: string
        - name: module
          required: false
          in: query
          schema:
            example: api
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DevTaskContextResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Собирает полный контекст задачи для dev agent
      tags:
        - internal
  /internal/sessions/{sessionId}/epics:
    post:
      operationId: InternalSessionsController_epic
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateEpicInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EpicResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Создаёт эпик backlog
      tags:
        - internal
  /internal/sessions/{sessionId}/events:
    get:
      description: >-
        Используется, чтобы ВОССТАНОВИТЬ историю разговорного агента и прочитать
        артефакты, нужные его kickoff. Эквивалентный маршрут для человека
        защищён RBAC; этот — сервисным токеном. `latest=true` возвращает ХВОСТ
        (всё ещё по возрастанию `seq`) и игнорирует `afterSeq`; `types`
        ограничивает страницу этими типами событий. Страница в любом случае
        ограничена 200 (ADR 0060).
      operationId: InternalSessionsController_listEvents
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
        - name: projectId
          required: true
          in: query
          schema:
            type: string
        - name: afterSeq
          required: false
          in: query
          schema:
            example: 40
            type: string
        - name: limit
          required: false
          in: query
          schema:
            example: 200
            type: string
        - name: latest
          required: false
          in: query
          description: Получает хвост журнала; игнорирует `afterSeq`.
          schema:
            example: 'true'
            type: string
        - name: types
          required: false
          in: query
          description: >-
            Типы событий через запятую (не более 20). Только события этих типов
            учитываются в `limit`.
          schema:
            example: artifact.product_brief,artifact.business_rule
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaginaDeEventosResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Постранично выдаёт журнал событий сессии для engine
      tags:
        - internal
    post:
      description: >-
        Тот же сценарий использования и то же атомарное присвоение `seq`, что и
        у маршрута для человека. Гипотезы Психолога сюда НЕ проходят: у них свой
        маршрут с проверкой доказательств.
      operationId: InternalSessionsController_appendEvent
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AppendSessionEventInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionEventResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Добавляет событие в журнал сессии от имени агента
      tags:
        - internal
  /internal/sessions/{sessionId}/gates/verdict:
    post:
      description: >-
        `nextAction` в ответе — то, чему подчиняется engine: `correct`
        возвращает к dev, `run_secops` продвигает gate, `done` передаёт
        пользователю, `await_user` ставит цикл на паузу до ответа на открытый
        вопрос (RN-860, без учёта исправления), а `blocked` значит, что лимит
        исправлений исчерпан. MERGE никогда не автоматический — `done` лишь
        означает, что очередь человека.
      operationId: InternalSessionsController_gateVerdict
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecordGateVerdictInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GateVerdictResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Записывает вердикт QA или SecOps по PR задачи
      tags:
        - internal
  /internal/sessions/{sessionId}/handoffs:
    post:
      description: >-
        Рождается как `offered`. Принимает ЧЕЛОВЕК, через маршрут для человека,
        — агент не активирует агента. Два исключения. Предложение Креатив→PO,
        несущее `product_brief`, которого попросил клик «Я готов — потребность
        подтверждена», принимается от имени кликнувшего, с этим человеком как
        актором и `implicito` в payload (RN-658, ADR 0185). Handoff от PO к
        Архитектору принимается СИСТЕМОЙ, когда backlog покрыт (хотя бы одно
        бизнес-правило, ни одного без истории), а репозиторий `local` и в
        проекте нет учётных данных git (RN-660, ADR 0186); `aceiteAutomatico`
        говорит, произошло ли это, и если нет — почему. В любом случае ответ
        затем содержит `status: accepted`.
      operationId: InternalSessionsController_handoff
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateHandoffInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OfertaInternaDeHandoffResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
        '409':
          description: >-
            `agente_ja_ativo` (ADR 0182, RN-635): целевой агент уже активен в
            незакрытой сессии проекта. `message` — текст, который агент читает
            как результат инструмента.
      security:
        - service-token: []
      summary: Предлагает handoff от одного агента другому
      tags:
        - internal
  /internal/sessions/{sessionId}/hypotheses:
    post:
      description: >-
        Каждая гипотеза должна ссылаться на события, которые СУЩЕСТВУЮТ в этой
        сессии, — выдуманные доказательства отклоняются с 400, и именно это
        отличает гипотезу от мнения. Предыдущий раунд сессии становится
        замещённым.
      operationId: InternalSessionsController_hypotheses
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProposeHypothesesInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProposeHypothesesResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
        '409':
          description: Для этой сессии уже существует текущий анализ.
      security:
        - service-token: []
      summary: Записывает раунд анализа и его гипотезы
      tags:
        - internal
  /internal/sessions/{sessionId}/infra-artifacts/{prActionId}/files:
    get:
      description: >-
        Содержимое берётся из собственного payload `proposed_action`:
        инфраструктурный артефакт никогда не касается worktree, так же как ADR.
      operationId: InternalSessionsController_infraPrFiles
      parameters:
        - name: projectId
          required: true
          in: query
          schema:
            type: string
        - name: prActionId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InfraPrFilesResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Возвращает файлы инфраструктурного PR для чтения gate
      tags:
        - internal
  /internal/sessions/{sessionId}/infra-context:
    get:
      description: >-
        Текущий module_map плюс относящиеся к делу инфраструктурные ADR, плюс
        маршрутизация модулей Архитектора (ADR 0131) — кандидаты, среди которых
        Infra Lead выбирает при предложении container_start.
      operationId: InternalSessionsController_infraContext
      parameters:
        - name: projectId
          required: true
          in: query
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InfraContextResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Собирает начальный контекст InfraAgent
      tags:
        - internal
  /internal/sessions/{sessionId}/infra-gates/verdict:
    post:
      description: Тот же конвейер и та же машина состояний, что и у PR dev.
      operationId: InternalSessionsController_infraGateVerdict
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecordInfraGateVerdictInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InfraGateVerdictResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Записывает вердикт QA или SecOps по инфраструктурному PR
      tags:
        - internal
  /internal/sessions/{sessionId}/instruction-patches:
    post:
      description: >-
        Становится `proposed_action`, а не прямой записью: изменение поведения
        агента — внешний эффект, и он проходит тот же конвейер одобрения, что и
        всё остальное. Это замыкает цикл гипотеза → патч.
      operationId: InternalSessionsController_instructionPatch
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProposeInstructionPatchInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProposedActionResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Предлагает патч к файлу инструкций агента
      tags:
        - internal
  /internal/sessions/{sessionId}/llm-turn:
    post:
      description: >-
        Учёт ОБЯЗАТЕЛЕН: каждый ход записывает `token_usage`, и именно это
        придаёт бюджету смысл. Не записывает никакого события — журнал событий
        ведёт engine. Сбой провайдера возвращается в `error` с 200, потому что
        учёт в `usage` остаётся действительным: ход потратил, даже завершаясь
        сбоем.
      operationId: InternalSessionsController_llmTurn
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RunLlmTurnDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LlmTurnResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: >-
            Бюджет превышен при `policy=block` или недействительный сервисный
            токен.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Выполняет учитываемый ход LLM с поддержкой инструментов
      tags:
        - internal
  /internal/sessions/{sessionId}/llm-turn-stream:
    post:
      description: >-
        Та же семантика, что и у `llm-turn`, доставляемая кадр за кадром. Кадр
        `done` несёт `usage` — без него ход вышел бы без учёта.
      operationId: InternalSessionsController_llmTurnStream
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StreamLlmTurnDto'
      responses:
        '200':
          description: Поток кадров до `done` или `error`.
          content:
            text/event-stream:
              schema:
                $ref: '#/components/schemas/LlmTurnStreamEventResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Выполняет ход LLM с потоковым ответом
      tags:
        - internal
  /internal/sessions/{sessionId}/max-parallel-proposals:
    post:
      description: >-
        Отказывается предлагать лимит, равный текущему или ниже его: Анамнез
        запускается периодически и предлагал бы одно и то же каждый раунд,
        заполняя шумом очередь, которую пользователю нужно читать.
      operationId: InternalSessionsController_maxParallelProposal
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProposeMaxParallelInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProposedActionResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Предлагает повысить лимит параллелизма области
      tags:
        - internal
  /internal/sessions/{sessionId}/module-contracts:
    post:
      description: >-
        Артефакт — ЭТО событие `artifact.module_contracts`: неизменяемое,
        версионированное и с автором, рядом с `artifact.module_map`. Каждый
        вызов несёт ВЕСЬ список и заменяет предыдущую версию. То, что модуль
        потребляет, здесь не пишется: это `dependsOn` текущего module_map.
      operationId: InternalSessionsController_moduleContracts
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeclareModuleContractsInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContratosDeclaradosResponseDto'
        '400':
          description: >-
            Пустой список, повторяющийся модуль, модуль вне текущего module_map
            (или нет module_map), модуль с пустым или слишком большим `expoe`,
            элемент с неизвестным `tipo` или отсутствующая/слишком длинная
            `assinatura`.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Объявляет новую версию контрактов между модулями
      tags:
        - internal
  /internal/sessions/{sessionId}/module-map:
    post:
      description: >-
        История неизменяема: каждая публикация — ещё одна версия, а текущая —
        та, у которой наибольший `version`. ЦИКЛ зависимостей между модулями
        приводит к ОТКЛОНЕНИЮ карты с 400 — граф должен быть ацикличным.
      operationId: InternalSessionsController_moduleMap
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateModuleMapInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModuleMapResponseDto'
        '400':
          description: Цикл зависимостей между модулями.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Публикует новую версию module_map
      tags:
        - internal
  /internal/sessions/{sessionId}/module-routing:
    post:
      description: >-
        Артефакт — ЭТО событие `artifact.module_routing`: неизменяемое,
        версионированное и с автором, рядом с
        `artifact.module_map`/`artifact.project_image`/`artifact.c4_diagram`.
        Каждый элемент проверяется тем же правилом, что и `choose_project_image`
        (явный тег/digest, `latest` отклоняется, нетривиальный `rationale`); имя
        модуля вне текущего module_map, пустой список или повторяющийся модуль
        отклоняются с 400.
      operationId: InternalSessionsController_moduleRouting
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RouteModulesToInfraInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RoteamentoDeModulosGeradoResponseDto'
        '400':
          description: >-
            Пустой список, повторяющийся модуль, модуль вне текущего module_map
            или образ-кандидат, не проходящий правило `choose_project_image`
            (нет явного тега, `latest` или короткий `rationale`).
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Назначает каждому модулю текущего module_map образ-кандидат
      tags:
        - internal
  /internal/sessions/{sessionId}/pending-work:
    get:
      description: >-
        Запрашивается `SessionServer` engine по таймауту heartbeat. `pending:
        true` заставляет перенести таймаут вместо закрытия, а `motivo` попадает
        в лог engine — сессию, которая не закрывается, не говоря почему,
        невозможно диагностировать.
      operationId: InternalSessionsController_pendingWork
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionPendingWorkResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Есть ли у сессии незавершённая работа, мешающая её закрыть?
      tags:
        - internal
  /internal/sessions/{sessionId}/proficiency:
    post:
      description: >-
        Компетенция вне каталога ОТКЛОНЯЕТСЯ, как и доказательство, ссылающееся
        на событие из другого проекта, — обе проверки существуют, потому что
        модель ошибается в обоих.
      operationId: InternalSessionsController_proficiency
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecordProficiencyInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecordProficiencyResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Записывает профили квалификации, выведенные в раунде
      tags:
        - internal
  /internal/sessions/{sessionId}/project-image:
    post:
      description: >-
        Артефакт — ЭТО событие `artifact.project_image`: неизменяемое,
        версионированное и с автором. Пересмотр означает выпуск новой версии,
        никогда не перезапись. Пока его нет, контейнер проекта не поднимается, а
        вкладка Code отвечает 409.
      operationId: InternalSessionsController_projectImage
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DecideProjectImageInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ImagemDecididaResponseDto'
        '400':
          description: >-
            Образ без явного тега (или `latest`), слишком короткий `rationale`,
            `network` вне {none, egress} или ресурс выше лимита.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Задаёт образ контейнера проекта
      tags:
        - internal
  /internal/sessions/{sessionId}/psychologist-context:
    get:
      description: >-
        `alreadyAnalyzed` даёт автоматическому пути ИДЕМПОТЕНТНОСТЬ: при `true`
        worker завершается сразу, не тратя ни одного токена. Предыдущие гипотезы
        приходят вместе, чтобы раунд не повторялся.
      operationId: InternalSessionsController_psychologistContext
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
        - name: projectId
          required: true
          in: query
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PsychologistContextResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Собирает контекст для раунда Психолога
      tags:
        - internal
  /internal/sessions/{sessionId}/semantic-duplicate-check:
    post:
      description: >-
        Встраивает заголовок и заголовки правил проекта (100 самых свежих)
        моделью эмбеддингов RAG и предупреждает при косинусе ≥ порога. Никогда
        не отклоняет. Без провайдера эмбеддингов проверка ПРОПУСКАЕТСЯ с
        указанием причины, также описанной в журнале событий. Расход на
        эмбеддинги учитывается отдельной строкой `token_usage` (актор
        `system`/`duplicata-semantica`).
      operationId: InternalSessionsController_semanticDuplicateCheck
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SemanticDuplicateCheckInternalDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SemanticDuplicateCheckResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: >-
        Предупреждает, когда только что записанное бизнес-правило похоже на
        существующее
      tags:
        - internal
  /internal/sessions/{sessionId}/stories:
    post:
      description: >-
        `businessRuleIds` питает покрытие правило→история. Каждый id должен
        ссылаться на СУЩЕСТВУЮЩЕЕ событие `artifact.business_rule` — проверка
        отклоняет выдуманный id. Ответ также содержит `semanticDuplicate`
        (RN-681): ПРЕДУПРЕЖДЕНИЕ на основе эмбеддингов по историям проекта,
        никогда не отказ.
      operationId: InternalSessionsController_story
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateStoryInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreatedStoryResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: >-
        Создаёт историю с функциональными/нефункциональными требованиями, DoD,
        DoR и покрытыми правилами
      tags:
        - internal
  /internal/sessions/{sessionId}/stories/{storyId}/archive:
    post:
      description: >-
        История и её задачи уходят из backlog, покрытия, плана и claim; строка
        остаётся, и `backlog.story_archived` фиксирует это с PO как актором
        (RN-727).
      operationId: InternalSessionsController_archiveStoryRoute
      parameters:
        - name: storyId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ArchiveStoryInternalDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StoryResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: История не в этом проекте.
        '409':
          description: >-
            Именованный отказ (`reason`): `historia_arquivada`,
            `historia_nao_draft` или `historia_com_tarefa_em_execucao`.
      security:
        - service-token: []
      summary: Архивирует черновую историю
      tags:
        - internal
  /internal/sessions/{sessionId}/stories/{storyId}/complete:
    post:
      description: >-
        `businessRuleIds` ДОБАВЛЯЕТСЯ к уже связанным правилам; непустые списки
        заменяют текущие, а опущенные остаются. После записи история
        продвигается по ТОМУ ЖЕ критерию, что и при создании (`ready` в режиме
        `auto`, `proposedReady` в `manual`) (RN-720).
      operationId: InternalSessionsController_completeStoryRoute
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
        - name: storyId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CompleteStoryInternalDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StoryResponseDto'
        '400':
          description: business_rule_id не ссылается на существующее правило.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: История не в этом проекте.
        '409':
          description: История больше не `draft`.
      security:
        - service-token: []
      summary: Дополняет существующую черновую историю вместо её повторного создания
      tags:
        - internal
  /internal/sessions/{sessionId}/stories/{storyId}/update:
    post:
      description: >-
        Только история `draft` без задачи в работе; пишет
        `backlog.story_updated` с PO как актором (RN-727). Также `ready`, когда
        `questionId` ссылается на открытый вопрос, на который пользователь уже
        ответил (RN-859).
      operationId: InternalSessionsController_updateStoryRoute
      parameters:
        - name: storyId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateStoryInternalDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StoryResponseDto'
        '400':
          description: Нет ни одного поля или пустой заголовок.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: История не в этом проекте.
        '409':
          description: >-
            Именованный отказ (`reason`): `historia_arquivada`,
            `historia_nao_draft` или `historia_com_tarefa_em_execucao`.
      security:
        - service-token: []
      summary: Исправляет заголовок и/или описание черновой истории
      tags:
        - internal
  /internal/sessions/{sessionId}/story-modules:
    post:
      description: >-
        Это перекрёстная проверка: модуль, которого нет в текущей карте,
        становится незакрытым вопросом архитектуры, а не проскальзывает
        незамеченным.
      operationId: InternalSessionsController_storyModules
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AssignStoryModulesInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StoryResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Связывает историю с модулями, которых она касается
      tags:
        - internal
  /internal/sessions/{sessionId}/tasks:
    post:
      operationId: InternalSessionsController_task
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateTaskInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Создаёт задачу внутри истории
      tags:
        - internal
  /internal/sessions/{sessionId}/tasks/{taskId}/block:
    post:
      description: >-
        Автоматической разблокировки нет: разблокирует человек, через маршрут
        для человека. Именно это не даёт агенту бесконечно крутиться.
      operationId: InternalSessionsController_blockTask
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
        - name: taskId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BlockTaskInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Помечает задачу как заблокированную с указанием причины
      tags:
        - internal
  /internal/sessions/{sessionId}/tasks/{taskId}/gate/open:
    post:
      description: Вызывается, когда PR dev agent готов к ревью.
      operationId: InternalSessionsController_openGateEndpoint
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
        - name: taskId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OpenGateInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GateAbertoResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Открывает конвейер gate задачи, начиная с QA
      tags:
        - internal
  /internal/sessions/{sessionId}/tasks/{taskId}/status:
    post:
      operationId: InternalSessionsController_markTaskStatus
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
        - name: taskId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MarkTaskInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
        '409':
          description: Недопустимый переход или задача другого агента.
      security:
        - service-token: []
      summary: Меняет состояние задачи по мере продвижения работы
      tags:
        - internal
  /internal/sessions/{sessionId}/tasks/{taskId}/unblock:
    post:
      description: >-
        Вызывается заданием повтора engine (до 3 раз, через 1, 3 и 10 мин). Тот
        же сценарий использования, что и разблокировка человеком, с актором
        `system`; задача, которая больше не заблокирована, остаётся нетронутой.
      operationId: InternalSessionsController_unblockTaskAutomatically
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
        - name: taskId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UnblockTaskInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Разблокирует задачу после временного сбоя сети/провайдера
      tags:
        - internal
  /internal/sessions/{sessionId}/tasks/claim:
    post:
      description: >-
        АТОМАРНО по замыслу: при нескольких dev agent на одном модуле два
        одновременных claim не могут вернуть одну и ту же задачу. Если доступной
        задачи нет, возвращает пустой результат вместо ошибки.
      operationId: InternalSessionsController_claimTask
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ClaimTaskInternalDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaskResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Атомарно забирает следующую доступную задачу модуля
      tags:
        - internal
  /internal/sessions/{sessionId}/tasks/in-progress-by-dev:
    get:
      description: >-
        Не заблокированные и вне gate PR; пусто, если сессия не является сессией
        выполнения. Используется engine при остановке выполнения, чтобы
        заблокировать те, которые не удерживает ни один живой dev agent.
      operationId: InternalSessionsController_tarefasEmCursoDeDev
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/TarefaEmCursoDeDevResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Перечисляет задачи проекта в работе, удерживаемые dev agent
      tags:
        - internal
  /internal/sessions/{sessionId}/termination:
    post:
      description: >-
        Сюда доходят только завершения, которые НЕ вызвала api, — падение, kill,
        `heartbeat_timeout`, защитное выключение. Остановка, запланированная
        самой api, сюда не проходит: engine уже узнал о ней через outbox, и
        сообщать о ней обратно было бы эхом.
      operationId: InternalSessionsController_report
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReportSessionTerminationDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionResponseDto'
        '400':
          description: Некорректное тело.
        '403':
          description: Сервисный токен отсутствует или отличается от общего.
        '404':
          description: Сессия, проект или ресурс не найдены.
      security:
        - service-token: []
      summary: Сообщает, что процесс сессии завершился в engine
      tags:
        - internal
  /live:
    get:
      description: >-
        НАМЕРЕННО НЕ касается базы данных. Если бы касался, медленный Postgres
        перезапустил бы ВСЕ реплики сразу и превратил деградацию в полный отказ.
        Pod убирается из балансировщика нагрузки через `/health`.
      operationId: HealthController_live
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthStatusResponseDto'
      summary: Liveness — процесс всё ещё обслуживает запросы
      tags:
        - infrastructure
  /llm/provider-capabilities:
    get:
      description: >-
        Слой ПРОВАЙДЕРА возможностей (ADR 0041) для девяти провайдеров,
        прочитанный из тех же экземпляров, что обслуживают вызовы. Возможность
        равна `true`, только если доказана против реального API. Экран читает
        отсюда `routingPreference`, прежде чем предложить предпочтение
        маршрутизации в binding (ADR 0166).
      operationId: ProviderCapabilitiesController_list
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ProviderCapabilitiesResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Перечисляет, что умеет каждый провайдер LLM, независимо от модели
      tags:
        - llm
  /metrics:
    get:
      description: >-
        Не JSON: это `text/plain` в формате экспозиции. Публичный по
        необходимости (сборщик не несёт токена) и ограничен СЕТЬЮ — Ingress
        продакшена блокирует этот путь на границе, а NetworkPolicy разрешает
        только пространство имён мониторинга.
      operationId: MetricsController_scrape
      parameters: []
      responses:
        '200':
          description: Метрики в формате экспозиции.
          content:
            text/plain:
              schema:
                type: string
              example: |
                # HELP oban_queue_depth Oban queue depth
                # TYPE oban_queue_depth gauge
                oban_queue_depth{queue="default",state="available"} 0
      summary: Отдаёт метрики в формате экспозиции Prometheus
      tags:
        - infrastructure
  /projects/{projectId}:
    get:
      operationId: ProjectsController_get
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует или невидим для вызывающего.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Возвращает проект по id
      tags:
        - projects
    patch:
      operationId: ProjectsController_update
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateProjectDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует или невидим для вызывающего.
        '409':
          description: Проект с таким slug уже существует в рабочем пространстве.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Меняет имя или slug проекта
      tags:
        - projects
    delete:
      description: Забирает с собой свои сессии, журнал событий и предложенные действия.
      operationId: ProjectsController_remove
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует или невидим для вызывающего.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Удаляет проект
      tags:
        - projects
  /projects/{projectId}/actions:
    get:
      description: >-
        `status` — это `pending` (по умолчанию) или `failed`. `actionType`
        фильтрует по типу — например, `git_merge`, чтобы вкладка PR нашла
        предложение merge для PR, не зная, какая сессия его создала.
        `status=failed` ТРЕБУЕТ `actionType` и перечисляет действия, выполнение
        которых завершилось сбоем, — вкладка PR читает отсюда последний
        отклонённый merge каждого PR (конфликт слияния несёт
        `executionResult.conflictingFiles`).
      operationId: ProjectActionsController_list
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: status
          required: false
          in: query
          schema:
            example: pending
            type: string
        - name: actionType
          required: false
          in: query
          schema:
            example: git_merge
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ProposedActionResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не найден.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Перечисляет ОЖИДАЮЩИЕ действия всего проекта, по любой сессии
      tags:
        - actions
  /projects/{projectId}/agent-areas:
    get:
      description: >-
        Проект рождается с тремя областями (RN-094), поэтому список возвращается
        полным даже до появления `module_map`. Активация выполнения добавляет
        УЧАСТНИКОВ области dev, по одному на модуль.
      operationId: ExecutionController_listAreas
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AreaDeAgentesResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или задача не найдены.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Области агентов проекта с лимитом каждого lead
      tags:
        - execution
  /projects/{projectId}/agent-areas/{key}/budget:
    put:
      description: >-
        Требует `maintainer`, по той же причине, что и лимит параллелизма: это
        НЕЗАВИСИМАЯ, дополнительная проверка рядом с бюджетами проекта и сессии
        (ADR 0110) — не каскад и не замена ни одному из них. Лимит приходит в
        ДОЛЛАРАХ и переводится в микро-USD на сервере, по тому же соглашению,
        что и `BudgetsController`. `null` снимает лимит.
      operationId: ExecutionController_setBudget
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: key
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SetAreaBudgetDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AreaDeAgentesResponseDto'
        '400':
          description: '`limitUsd` не является ни `null`, ни числом >= 0.'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или задача не найдены.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Задаёт (или снимает) лимит расходов области
      tags:
        - execution
  /projects/{projectId}/agent-areas/{key}/max-parallel:
    patch:
      description: >-
        Требует `maintainer`, а не `developer`, по той же причине, что и
        активация выполнения: повышать лимит — значит решать, сколько продукт
        может тратить, не спрашивая. Применяется к СЛЕДУЮЩИМ запросам — то, что
        уже ждёт вашего решения, продолжает ждать.
      operationId: ExecutionController_setMaxParallel
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: key
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SetMaxParallelDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AreaDeAgentesResponseDto'
        '400':
          description: '`maxParallel` не является целым числом >= 1.'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или задача не найдены.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Меняет лимит параллелизма области
      tags:
        - execution
  /projects/{projectId}/agent-autonomy:
    get:
      description: >-
        Только явно записанные правила. Агент без строки здесь откатывается к
        значению по умолчанию из `permissions.json` проекта.
      operationId: AgentAutonomyController_list
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AgentAutonomyRuleResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не найден.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Перечисляет автономию, предоставленную каждому агенту
      tags:
        - actions
    put:
      description: >-
        Upsert по (агент, тип). НЕ переопределяет `permissions.json`: шаблон,
        уже находящийся в `deny`, остаётся заблокированным, сколько бы автономии
        здесь ни было предоставлено. `actionType: "*"` — это «автоматический
        режим» (RN-153), автономия для ЛЮБОГО типа действия этого агента;
        конкретное правило, записанное после, всё равно побеждает подстановочное
        для этого типа.
      operationId: AgentAutonomyController_set
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SetAgentAutonomyDto'
      responses:
        '204':
          description: Правило записано. Без тела.
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не найден.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Задаёт автономию агента для типа действия
      tags:
        - actions
  /projects/{projectId}/agent-bindings/{agentSlug}:
    get:
      description: >-
        Каскад агент → область → проект → рабочее пространство, БЕЗ сессии: это
        конфигурация агента в проекте, а не конкретного разговора. `origin:
        "agent"` означает, что этот агент ОТКЛОНИЛСЯ от значения по умолчанию
        своей области; любой другой origin означает, что он наследует.
      operationId: ModelBindingsController_getAgentBinding
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: agentSlug
          required: true
          in: path
          schema:
            example: dev-api
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResolvedBindingResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в области действия.
        '404':
          description: Область действия не найдена.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Определяет, какую модель использует агент и откуда она взялась
      tags:
        - llm
    put:
      description: >-
        Применяется к каждой сессии этого проекта, у которой нет собственного
        binding, и только к ним — до ADR 0064 binding был глобальным и
        затрагивал и другие проекты. Так Психологу даётся дешёвая модель, а
        Архитектору дорогая в одном проекте, и так же агент ОТКЛОНЯЕТСЯ от
        значения по умолчанию своей области.
      operationId: ModelBindingsController_setAgentBinding
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: agentSlug
          required: true
          in: path
          schema:
            example: dev-api
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SetModelBindingDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelBindingResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в области действия.
        '404':
          description: Область действия не найдена.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Закрепляет модель агента в ЭТОМ проекте
      tags:
        - llm
    delete:
      description: >-
        УДАЛЯЕТ binding агента — не копирует в него модель области. Копирование
        выглядело бы на экране так же, но им не является: оно стало бы копией, и
        следующее изменение области молча оставило бы этого агента позади. 404,
        если он уже наследует.
      operationId: ModelBindingsController_clearAgentBinding
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: agentSlug
          required: true
          in: path
          schema:
            example: dev-api
            type: string
      responses:
        '204':
          description: Агент снова наследует. Без тела.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в области действия.
        '404':
          description: Область действия не найдена.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Возвращает агента к наследованию модели области
      tags:
        - llm
  /projects/{projectId}/agent-costs:
    get:
      description: >-
        СКОЛЬЗЯЩЕЕ окно в 30 дней, а не текущий календарный месяц: именно эту
        подпись показывает экран, а с календарным месяцем оценка проваливалась
        бы 1-го числа из-за смены страницы, а не из-за изменения использования.
        Учитывается только `actor_kind = agent` (RN-038). Агент, который никогда
        не запускался, не появляется в списке — экран показывает для него
        прочерк, что отличается от нуля.
      operationId: BudgetsController_getProjectAgentCosts
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AgentTokenUsageResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект или сессия не найдены.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Разбивает расходы проекта по агентам за последние 30 дней
      tags:
        - llm
  /projects/{projectId}/agents/{agent}/instruction-versions:
    get:
      description: >-
        Сначала самые свежие, каждая уже с diff относительно предыдущей,
        вычисленным на сервере.
      operationId: AnamneseController_versions
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: agent
          required: true
          in: path
          schema:
            example: dev-api
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/InstructionVersionResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, событие или версия не существуют.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Перечисляет версии инструкции агента
      tags:
        - anamnesis
  /projects/{projectId}/agents/{agent}/instruction-versions/{version}/rollback:
    post:
      description: >-
        История неизменяема: восстановление ничего не удаляет, оно СОЗДАЁТ новую
        версию со старым содержимым. Требует `maintainer`, потому что меняет
        поведение агента с этого момента, — та же калибровка, что и у патча.
      operationId: AnamneseController_rollbackVersion
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: agent
          required: true
          in: path
          schema:
            example: dev-api
            type: string
        - name: version
          required: true
          in: path
          description: Версия для восстановления.
          schema:
            example: 2
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RollbackResponseDto'
        '400':
          description: Версия не является положительным целым числом.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, событие или версия не существуют.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Восстанавливает предыдущую версию инструкции агента
      tags:
        - anamnesis
  /projects/{projectId}/anamnese/run:
    post:
      description: >-
        Требует `maintainer` по той же причине, что и повторный анализ
        Психолога: он запускает ToolLoop и тратит реальный бюджет.
      operationId: AnamneseController_run
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OkResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, событие или версия не существуют.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
        '503':
          description: >-
            Анамнез глобально отключён по решению пользователя (не ошибка) —
            тело с `reason: "anamnese_disabled"`.
      security:
        - bearer: []
      summary: Запускает Анамнез сейчас, не дожидаясь тика
      tags:
        - anamnesis
  /projects/{projectId}/architecture:
    get:
      description: >-
        Текущая карта — та, у которой наибольший `version` (история
        неизменяема). Незакрытые вопросы — это перекрёстная проверка
        история↔карта: история без модуля или указывающая на несуществующий
        модуль.
      operationId: BacklogController_architecture
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ArchitectureResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Возвращает текущий module_map, ADR и незакрытые вопросы
      tags:
        - backlog
  /projects/{projectId}/area-bindings/{areaKey}:
    get:
      description: >-
        Значение по умолчанию, общее для lead и субагентов области. `origin:
        "area"` означает, что кто-то выбрал его для этой области; любой другой
        origin означает, что сама область наследует от проекта или рабочего
        пространства.
      operationId: ModelBindingsController_getAreaBinding
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: areaKey
          required: true
          in: path
          schema:
            example: qa
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResolvedBindingResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в области действия.
        '404':
          description: Область действия не найдена.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Определяет, какая модель области по умолчанию и откуда она взялась
      tags:
        - llm
    put:
      description: >-
        Применяется к lead и к каждому субагенту области, у которого нет
        собственного binding (RN-102). Требует `maintainer`, потому что
        затрагивает всю область сразу, а выбор модели — это решение о расходах,
        та же причина, что и у лимита параллелизма.
      operationId: ModelBindingsController_setAreaBinding
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: areaKey
          required: true
          in: path
          schema:
            example: qa
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SetModelBindingDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelBindingResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в области действия.
        '404':
          description: Область действия не найдена.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Задаёт модель области по умолчанию
      tags:
        - llm
    delete:
      description: >-
        Область перестаёт иметь собственное значение по умолчанию и начинает
        наследовать от проекта (или рабочего пространства). Отклонившиеся агенты
        продолжают отклоняться: их binding другой, и удаление этого не может
        решать за них.
      operationId: ModelBindingsController_clearAreaBinding
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: areaKey
          required: true
          in: path
          schema:
            example: qa
            type: string
      responses:
        '204':
          description: Область снова наследует. Без тела.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в области действия.
        '404':
          description: Область действия не найдена.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Возвращает область к наследованию модели проекта
      tags:
        - llm
  /projects/{projectId}/backlog:
    get:
      description: >-
        Вложенное и полное, за один вызов. АРХИВНЫЕ истории (RN-727) не
        перечисляются. Backlog пишут агенты, через маршруты `/internal/*`.
      operationId: BacklogController_backlog
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/EpicComHistoriasResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Возвращает дерево эпик → история → задача
      tags:
        - backlog
  /projects/{projectId}/budget:
    get:
      description: Значения в микро-USD. Требует `maintainer` — это информация о затратах.
      operationId: BudgetsController_getProjectBudget
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BudgetResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект или сессия не найдены.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Читает бюджет проекта и сколько уже потрачено
      tags:
        - llm
    put:
      description: >-
        Лимит приходит в ДОЛЛАРАХ и переводится в микро-USD. При `policy=block`
        вызов, который превысил бы лимит, ОТКЛОНЯЕТСЯ, а не просто записывается.
      operationId: BudgetsController_setProjectBudget
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpsertBudgetDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BudgetResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект или сессия не найдены.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Задаёт лимит расходов проекта
      tags:
        - llm
  /projects/{projectId}/code/blame:
    get:
      description: >-
        Основа для заявленного незакрытого вопроса blame (ФАЗА 26b) —
        интерфейса, аннотирующего редактор построчно, ещё нет. `truncated`
        предупреждает, когда файл превысил лимит аннотируемых строк.
      operationId: CodeController_blame
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: path
          required: true
          in: query
          description: Путь к файлу для аннотирования.
          schema:
            type: string
        - name: ref
          required: false
          in: query
          description: >-
            Ветка, тег или sha. Если отсутствует, используется ветка репозитория
            по умолчанию.
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CodeBlameResponseDto'
        '400':
          description: >-
            Путь вне области проекта (RN-095), некорректный ref или слишком
            короткий либо слишком длинный поисковый запрос.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект без созданного репозитория или несуществующий ref/путь/PR.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
        '501':
          description: >-
            Провайдер проекта не объявляет возможность операции — `listTree`,
            `pullRequestDiff`, `blame`, `pullRequestsList` или
            `branchesDetailed`. Возможность объявляется только после
            доказательства набором контрактных тестов, поэтому это законный
            ответ, а не дефект.
      security:
        - bearer: []
      summary: Аннотирует каждую строку файла коммитом, который её затронул
      tags:
        - git
  /projects/{projectId}/code/branches:
    get:
      description: >-
        Основа для расширенного выпадающего списка (ФАЗА 26b) — сегодня селектор
        ref является простым текстовым полем. `ahead`/`behind` отсчитываются от
        ветки репозитория ПО УМОЛЧАНИЮ; `null`, когда провайдер не может это
        вычислить (честная деградация, а не ошибка). Это НЕ `listBranches`
        (который использует bootstrap Gitflow) — это отдельная операция, потому
        что обогащение стоит лишнего вызова провайдера НА КАЖДУЮ ВЕТКУ.
      operationId: CodeController_branches
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CodeBranchDetailListResponseDto'
        '400':
          description: >-
            Путь вне области проекта (RN-095), некорректный ref или слишком
            короткий либо слишком длинный поисковый запрос.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект без созданного репозитория или несуществующий ref/путь/PR.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
        '501':
          description: >-
            Провайдер проекта не объявляет возможность операции — `listTree`,
            `pullRequestDiff`, `blame`, `pullRequestsList` или
            `branchesDetailed`. Возможность объявляется только после
            доказательства набором контрактных тестов, поэтому это законный
            ответ, а не дефект.
      security:
        - bearer: []
      summary: Перечисляет ветки с ahead/behind и связанным открытым PR
      tags:
        - git
  /projects/{projectId}/code/file:
    get:
      description: >-
        UTF-8, обрезается на лимите байтов с `truncated: true`, если он
        превышен. Путь, выходящий за папку проекта, отклоняется с 400
        центральной проверкой из RN-095 — никогда с 404, что приглашало бы к
        зондированию.
      operationId: CodeController_file
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: path
          required: true
          in: query
          description: Путь к файлу.
          schema:
            type: string
        - name: ref
          required: false
          in: query
          description: Ветка, тег или sha.
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CodeFileResponseDto'
        '400':
          description: >-
            Путь вне области проекта (RN-095), некорректный ref или слишком
            короткий либо слишком длинный поисковый запрос.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект без созданного репозитория или несуществующий ref/путь/PR.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
        '501':
          description: >-
            Провайдер проекта не объявляет возможность операции — `listTree`,
            `pullRequestDiff`, `blame`, `pullRequestsList` или
            `branchesDetailed`. Возможность объявляется только после
            доказательства набором контрактных тестов, поэтому это законный
            ответ, а не дефект.
      security:
        - bearer: []
      summary: Возвращает содержимое файла
      tags:
        - git
  /projects/{projectId}/code/pull-requests:
    get:
      description: >-
        Основа для навигируемого списка (ФАЗА 26b) — сегодня diff доступен
        только по известному id (например, пришедшему из Одобрений); этот
        маршрут решает, как дойти до id, не зная его наизусть. `truncated`
        предупреждает, когда список превысил лимит на вызов.
      operationId: CodeController_pullRequests
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: state
          required: false
          in: query
          description: Если отсутствует, перечисляются все состояния.
          schema:
            enum:
              - open
              - merged
              - closed
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CodePullRequestListResponseDto'
        '400':
          description: >-
            Путь вне области проекта (RN-095), некорректный ref или слишком
            короткий либо слишком длинный поисковый запрос.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект без созданного репозитория или несуществующий ref/путь/PR.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
        '501':
          description: >-
            Провайдер проекта не объявляет возможность операции — `listTree`,
            `pullRequestDiff`, `blame`, `pullRequestsList` или
            `branchesDetailed`. Возможность объявляется только после
            доказательства набором контрактных тестов, поэтому это законный
            ответ, а не дефект.
      security:
        - bearer: []
      summary: Перечисляет PR/MR репозитория
      tags:
        - git
  /projects/{projectId}/code/pull-requests/{pullRequestId}/diff:
    get:
      description: >-
        Нулевой `patch` отличает случай, когда провайдер не передал текст
        (бинарный файл или слишком большой патч), от случая, когда текст
        вернулся пустым. Слияние двух заставило бы экран говорить, что
        изменённый бинарный файл не изменился. Поле `truncated` предупреждает,
        когда список файлов превысил лимит.
      operationId: CodeController_diff
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: pullRequestId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CodeDiffResponseDto'
        '400':
          description: >-
            Путь вне области проекта (RN-095), некорректный ref или слишком
            короткий либо слишком длинный поисковый запрос.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект без созданного репозитория или несуществующий ref/путь/PR.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
        '501':
          description: >-
            Провайдер проекта не объявляет возможность операции — `listTree`,
            `pullRequestDiff`, `blame`, `pullRequestsList` или
            `branchesDetailed`. Возможность объявляется только после
            доказательства набором контрактных тестов, поэтому это законный
            ответ, а не дефект.
      security:
        - bearer: []
      summary: Возвращает diff PR, нормализованный между провайдерами
      tags:
        - git
  /projects/{projectId}/code/search:
    get:
      description: >-
        Поиск НЕ является операцией контракта git: он составлен из дерева и
        содержимого, и поэтому это единственное чтение, стоимость которого
        растёт с размером репозитория. Его ограничивают три бюджета — пройденные
        каталоги, открытые файлы и возвращённые совпадения, — а `truncated`
        говорит, что он остановился. `filesScanned` показывает понесённые
        затраты.
      operationId: CodeController_search
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: q
          required: true
          in: query
          description: Запрос, от 2 до 200 символов.
          schema:
            type: string
        - name: ref
          required: false
          in: query
          description: Ветка, тег или sha.
          schema:
            type: string
        - name: path
          required: false
          in: query
          description: Поддерево для поиска. Сужение именно позволяет избежать обрезки.
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CodeSearchResponseDto'
        '400':
          description: >-
            Путь вне области проекта (RN-095), некорректный ref или слишком
            короткий либо слишком длинный поисковый запрос.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект без созданного репозитория или несуществующий ref/путь/PR.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
        '501':
          description: >-
            Провайдер проекта не объявляет возможность операции — `listTree`,
            `pullRequestDiff`, `blame`, `pullRequestsList` или
            `branchesDetailed`. Возможность объявляется только после
            доказательства набором контрактных тестов, поэтому это законный
            ответ, а не дефект.
      security:
        - bearer: []
      summary: Ищет текст в репозитории, с бюджетом
      tags:
        - git
  /projects/{projectId}/code/tree:
    get:
      description: >-
        Не рекурсивно, по замыслу контракта: вкладка навигирует по требованию, а
        запрос всего дерева большого репозитория — именно тот усилитель трафика,
        который эта фаза запрещает. `truncated` предупреждает, когда уровень
        превысил лимит записей.
      operationId: CodeController_tree
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: ref
          required: false
          in: query
          description: >-
            Ветка, тег или sha. Если отсутствует, используется ветка репозитория
            по умолчанию.
          schema:
            type: string
        - name: path
          required: false
          in: query
          description: Каталог для перечисления. Отсутствующий или пустой — корень.
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CodeTreeResponseDto'
        '400':
          description: >-
            Путь вне области проекта (RN-095), некорректный ref или слишком
            короткий либо слишком длинный поисковый запрос.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект без созданного репозитория или несуществующий ref/путь/PR.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
        '501':
          description: >-
            Провайдер проекта не объявляет возможность операции — `listTree`,
            `pullRequestDiff`, `blame`, `pullRequestsList` или
            `branchesDetailed`. Возможность объявляется только после
            доказательства набором контрактных тестов, поэтому это законный
            ответ, а не дефект.
      security:
        - bearer: []
      summary: Перечисляет ОДИН уровень дерева репозитория
      tags:
        - git
  /projects/{projectId}/container:
    get:
      description: >-
        Пока статус `sem_decisao`, контейнер проекта не поднимается, а вкладка
        Code отвечает 409 — это gate RN-105. Чтобы видеть состояние, нужно то же
        разрешение, что и для просмотра проекта, поэтому `viewer`.
      operationId: ContainersController_estado
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EstadoDoContainerResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Состояние контейнера проекта (текущее решение об образе)
      tags:
        - projects
  /projects/{projectId}/container/lifecycle:
    get:
      description: >-
        `null`, когда проект никогда не создавался, — сегодня это обычный
        случай, потому что ни один реальный оркестратор ещё не переводит эту
        таблицу (RN-243/RN-267). `status`/`imageVersion`/`resources` — это то,
        что было ЗАПИСАНО; `observado` — то, что демон Docker сообщает прямо
        сейчас, по запросу через broker (ADR 0130). Они никогда не сливаются:
        контейнер, убитый извне, читается как записанный `running` и наблюдаемый
        `exited`. Когда посмотреть не удалось, `observado` равен `null`, а
        `naoObservado` говорит почему — он никогда не наследует записанное
        состояние (RN-468).
      operationId: ContainersController_cicloDeVida
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: Записанный жизненный цикл или `null` (никогда не создавался).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CicloDeVidaDoContainerResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Жизненный цикл контейнера проекта (записанное состояние)
      tags:
        - projects
  /projects/{projectId}/container/network:
    get:
      description: >-
        The current declaration (ports on 127.0.0.1, DNS, extra hosts), or
        `null`. It is what was DECLARED; it only takes effect when the container
        is recreated (`container_recreate`).
      operationId: ContainersController_rede
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: Or `null`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RedeDoContainerResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Declared network of the container (artifact.container_network)
      tags:
        - projects
    put:
      description: >-
        Emits a new version of `artifact.container_network` (the current one
        replaces the previous). Not an external effect: nothing changes in
        Docker until a `container_recreate` is approved (AT-608, RN-932). 400
        `rede_invalida` names the field.
      operationId: ContainersController_declarar
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeclararRedeDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RedeDoContainerResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Declares the container network (human only)
      tags:
        - projects
  /projects/{projectId}/coverage:
    get:
      description: >-
        Сопоставляет правила проекта с `businessRuleIds` историй. Правило вообще
        без истории — это ОТКРЫТИЕ, незакрытый вопрос для PO, а не ошибка.
      operationId: BacklogController_coverage
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CoverageReportResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Вычисляет покрытие бизнес-правило → истории
      tags:
        - backlog
  /projects/{projectId}/events/{eventId}:
    get:
      description: >-
        В отличие от маршрута события по сессии: здесь сессия не известна.
        Именно это позволяет фишке доказательства в профиле попадать на нужное
        событие, поскольку окно Анамнеза охватывает несколько сессий.
      operationId: AnamneseController_event
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: eventId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionEventResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, событие или версия не существуют.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Возвращает событие проекта по id, определяя его сессию
      tags:
        - anamnesis
  /projects/{projectId}/execution-mode:
    put:
      description: >-
        Переносит `execution_mode`/`workspacePath` для УЖЕ существующего проекта
        (RN-447..450, ADR 0111) — отдельный маршрут, отделённый от PATCH, потому
        что он оркестрирует больше, чем запись столбца: переносит
        permissions.json в новый корень области, выводит из обращения строку
        жизненного цикла контейнера при выходе из режима `container` и отклоняет
        (409), пока любой dev agent проекта не простаивает, поскольку работающий
        агент сам не пересчитывает расположение своего worktree.
      operationId: ProjectsController_convertExecutionModeRoute
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ConvertExecutionModeDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует или невидим для вызывающего.
        '409':
          description: Dev agent этого проекта сейчас работает или завис.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Конвертирует режим выполнения проекта
      tags:
        - projects
  /projects/{projectId}/execution/activate:
    post:
      description: >-
        Требует `maintainer`, а не `developer`, потому что git-действия dev
        agent наследуют роль ТОГО, КТО АКТИВИРОВАЛ, в оценке IAM — активация с
        ролью developer оставила бы агентов без возможности открыть PR. Каждый
        модуль в `module_map` получает агента в изолированном worktree.
      operationId: ExecutionController_activate
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ActivateExecutionDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExecucaoAtivadaResponseDto'
        '400':
          description: >-
            Нет текущего `module_map` (Архитектор сначала должен определить
            модули), или сохранённое расположение рабочей области проекта
            несогласованно — `permissions.json`, засеваемый здесь, не может быть
            из него выведен (RN-478).
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или задача не найдены.
        '409':
          description: >-
            У проекта ещё нет репозитория (RN-582): dev agent работают в его
            worktree, поэтому ничего не запускается. Сообщение называет, чего не
            хватает, — handoff к Архитектору (или, как вторая дверь, к Dev
            Lead), принятие которого создаёт его, или страницу создания, если
            этот handoff уже был принят и не оставил репозитория. Также:
            исходная сессия `consultiva` и отклоняет `execution.activated`
            (RN-097). Повторная активация НЕ является конфликтом: она
            идемпотентна через `findActiveExecutionSession` и повторно
            активирует внутри той же сессии.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Активирует фазу выполнения и запускает по одному dev agent на модуль
      tags:
        - execution
  /projects/{projectId}/execution/session:
    get:
      description: >-
        Самая свежая сессия `active`, уже несущая `execution.activated`, — ТОТ
        ЖЕ критерий, который `activate` использует, чтобы решить, активировать
        повторно или создать (RN-139). `null`, когда выполнение не идёт.
        Существует, чтобы вкладка Исполнители перестала выводить эту сессию из
        самой свежей сессии проекта, которая меняется от сессии к сессии без
        всякой подсказки в интерфейсе.
      operationId: ExecutionController_getSession
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: '`null` в теле, когда нет активного выполнения.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или задача не найдены.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Возвращает текущую сессию выполнения проекта или ничего
      tags:
        - execution
  /projects/{projectId}/git/{provider}/connect:
    get:
      description: >-
        Не перенаправляет: возвращает URL, чтобы клиент решил, когда отправить
        браузер. `state` подписан HMAC, и именно это не даёт подделать callback.
      operationId: GitController_connect
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: provider
          required: true
          in: path
          schema:
            enum:
              - github
              - gitlab
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GitAuthorizeUrlResponseDto'
        '400':
          description: Провайдер вне `github`/`gitlab`.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Начинает OAuth с git-провайдером
      tags:
        - git
  /projects/{projectId}/git/{provider}/repository:
    post:
      description: >-
        СИНХРОННО: ответ приходит только после выполнения всего bootstrap
        (постоянные ветки, защиты, шаблоны) — за этим нет ни worker, ни очереди.
        Это ИДЕМПОТЕНТНО И ВОЗОБНОВЛЯЕМО, поэтому повторный вызов после сбоя
        продолжает с шага, на котором произошёл сбой. Следите за прогрессом и
        читайте причину сбоя через `GET /projects/:id/git/bootstrap`.
      operationId: GitController_provision
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: provider
          required: true
          in: path
          schema:
            enum:
              - local
              - github
              - gitlab
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProvisionRepositoryDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProvisionRepositoryResponseDto'
        '400':
          description: Недопустимый провайдер или отсутствуют учётные данные.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Создаёт репозиторий и выполняет bootstrap Gitflow
      tags:
        - git
  /projects/{projectId}/git/{provider}/repository/adopt:
    post:
      description: >-
        Проверяет доступ через `getRepo` и составляет ПЛАН (dry-run) того, что
        сделал бы bootstrap, — недостающие ветки, недостающие защиты, файлы. В
        репозитории НИЧЕГО не выполняется: решите потом через `plan/approve` или
        `plan/skip`. Повторное подключение того же репозитория сходится (план
        генерируется заново без дублирования строки).
      operationId: GitController_adopt
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: provider
          required: true
          in: path
          schema:
            enum:
              - local
              - github
              - gitlab
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AdoptRepositoryDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AdoptRepositoryResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: >-
            Репозиторий существует, но зарегистрированные учётные данные не дают
            до него добраться.
        '404':
          description: Репозиторий не существует у провайдера — проверьте идентификатор.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Подключает существующий репозиторий, ничего не создавая
      tags:
        - git
  /projects/{projectId}/git/bootstrap:
    get:
      description: >-
        На каком шаге он находится, был ли сбой и где, и сколько было попыток.
        Пошаговые детали берутся из событий `bootstrap.step_*` выделенной
        сессии.
      operationId: GitController_getBootstrap
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RepoBootstrapStatusResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Возвращает состояние bootstrap Gitflow
      tags:
        - git
  /projects/{projectId}/git/bootstrap/acknowledge-protection-failure:
    post:
      description: >-
        `protect_branches` завершается сбоем в приватном репозитории на
        бесплатном тарифе, и мастер предупреждает об этом ЗАРАНЕЕ. Без этого
        выхода единственной кнопкой было «Попробовать снова», что всегда
        проваливается по той же причине, — а `provision_failed` заставляет
        панель перенаправлять проект обратно на страницу создания, делая его
        недоступным навсегда. Подтвердить можно только сбой при ЗАЩИТЕ: это
        последний шаг и единственный, сбой которого оставляет пригодный
        репозиторий. Блокировка merge продукта (RN-006) не зависит от защиты
        провайдера и продолжает действовать.
      operationId: GitController_reconhecerFalhaDeProtecao
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReconhecerFalhaDeProtecaoResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует.
        '409':
          description: >-
            Bootstrap не завершился сбоем, или сбой произошёл на шаге до защиты
            — продолжать там означало бы оставить проект без пригодного
            репозитория.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Подтверждает сбой защиты веток и продолжает
      tags:
        - git
  /projects/{projectId}/git/bootstrap/plan:
    get:
      description: >-
        `decision: null` при наличии плана — это состояние, в котором НИЧЕГО не
        выполняется: ожидание одобрения или подключения как есть (RN-045).
      operationId: GitController_plan
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BootstrapPlanEstadoResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Возвращает план bootstrap и принятое по нему решение
      tags:
        - git
  /projects/{projectId}/git/bootstrap/plan/approve:
    post:
      description: >-
        Одобрение — всё или ничего: одобрение отдельных шагов сломало бы каскад
        `dev←main, qa←dev, rc←qa`. Выполняется план, ЗАНОВО ВЫВЕДЕННЫЙ в момент
        выполнения, — равный показанному или меньше, но никогда не больше.
      operationId: GitController_approvePlan
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DecideBootstrapPlanDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DecideBootstrapPlanResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует.
        '409':
          description: >-
            План был пересоздан с момента, когда вы его видели, или по нему уже
            было принято решение.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Одобряет ВЕСЬ план и выполняет bootstrap
      tags:
        - git
  /projects/{projectId}/git/bootstrap/plan/skip:
    post:
      description: >-
        Записывает решение и не трогает репозиторий. План остаётся сохранённым
        как доказательство того, что сознательно не было применено, а курсор
        bootstrap НЕ изменяется — ни один шаг не выполнялся.
      operationId: GitController_skipPlan
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DecideBootstrapPlanDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DecideBootstrapPlanResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует.
        '409':
          description: >-
            План был пересоздан с момента, когда вы его видели, или по нему уже
            было принято решение.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Подключает репозиторий КАК ЕСТЬ, отказываясь от bootstrap
      tags:
        - git
  /projects/{projectId}/git/repository:
    get:
      operationId: GitController_get
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProvisionedRepositoryResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Возвращает созданный репозиторий проекта
      tags:
        - git
  /projects/{projectId}/hypotheses:
    get:
      description: >-
        Каждая гипотеза ссылается на события, которые её подтверждают
        (`evidenceEventIds`), — именно это делает её проверяемой, а не
        принимаемой на веру.
      operationId: PsychologistController_hypotheses
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/HypothesisResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или гипотеза не существуют.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Перечисляет гипотезы Психолога об агентах
      tags:
        - psychologist
  /projects/{projectId}/hypotheses/{hypothesisId}/accept:
    post:
      description: >-
        Переводит `proposed` → `accepted`. НЕ `proposed_action`: Психолог только
        читает, и здесь нет ничего с внешним эффектом.
      operationId: PsychologistController_accept
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: hypothesisId
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HypothesisResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или гипотеза не существуют.
        '409':
          description: По гипотезе уже принято решение.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Принимает гипотезу
      tags:
        - psychologist
  /projects/{projectId}/hypotheses/{hypothesisId}/dismiss:
    post:
      description: Переводит `proposed` → `dismissed`. Та же логика, что и при принятии.
      operationId: PsychologistController_dismiss
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: hypothesisId
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HypothesisResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или гипотеза не существуют.
        '409':
          description: По гипотезе уже принято решение.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Отклоняет гипотезу
      tags:
        - psychologist
  /projects/{projectId}/infra-artifacts:
    get:
      description: >-
        Артефакты InfraAgent (Dockerfile, compose, CI). Они проходят ТЕ ЖЕ gate
        QA и SecOps, что и PR dev, без задачи или worktree за ними.
      operationId: BacklogController_infraArtifacts
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/InfraArtifactResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Перечисляет инфраструктурные PR и этап gate каждого
      tags:
        - backlog
  /projects/{projectId}/instruction-versions:
    get:
      description: >-
        Исходит из тех, у кого ЕСТЬ версия в проекте, а не из статического
        списка — именно поэтому появляются dev agent по модулям (`dev-api`).
      operationId: AnamneseController_allVersions
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AgenteComVersoesResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, событие или версия не существуют.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Перечисляет историю инструкций каждого агента проекта
      tags:
        - anamnesis
  /projects/{projectId}/members:
    get:
      description: >-
        Только те, кто был связан ЗДЕСЬ, с ролью этой связи, — тот, кто попадает
        в проект только через рабочее пространство, не имеет строки и не
        появляется.
      operationId: ProjectsController_listMembers
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ProjectMemberComUsuarioResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует или невидим для вызывающего.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Перечисляет участников проекта
      tags:
        - projects
    post:
      description: >-
        Эта роль ПЕРЕОПРЕДЕЛЯЕТ то, что у человека есть в рабочем пространстве,
        в обе стороны: связывание кого-то как `viewer` здесь действительно
        ограничивает `developer` рабочего пространства в этом проекте. Два
        действия отклоняются с 403 и не могут быть нигде включены: понижение
        `owner` рабочего пространства и изменение СОБСТВЕННОЙ роли — вниз или
        вверх, поскольку самоповышение — та половина, которая повышает
        привилегии.
      operationId: ProjectsController_addMember
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AddMemberDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectMemberResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: >-
            Недостаточная роль в проекте ИЛИ один из двух потолков (цель —
            `owner` рабочего пространства; цель — сам вызывающий, и запрошенная
            роль отличается от его текущей эффективной).
        '404':
          description: Проект не существует или невидим для вызывающего.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Связывает пользователя с проектом
      tags:
        - projects
  /projects/{projectId}/members/{userId}:
    delete:
      description: >-
        Удаляет только связь с ПРОЕКТОМ. Тот, у кого есть роль в рабочем
        пространстве, продолжает видеть проект по наследованию, — и поскольку
        роль проекта ПЕРЕОПРЕДЕЛЯЕТ роль рабочего пространства в обе стороны,
        это наследование может быть НИЖЕ того, что давала строка. Поэтому
        удаление собственной строки отклоняется с 403 всякий раз, когда итоговый
        эффект — понижение (включая случай, когда роли в рабочем пространстве
        нет вовсе); удаление, когда рабочее пространство держит ту же роль, и
        удаление кого-либо другого по-прежнему работают.
      operationId: ProjectsController_removeMember
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: userId
          required: true
          in: path
          schema:
            type: string
      responses:
        '204':
          description: Связь удалена. Без тела.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: >-
            Недостаточная роль в проекте ИЛИ вызывающий удаляет собственную
            строку и в итоге получил бы роль ниже, чем сейчас.
        '404':
          description: Проект не существует или невидим для вызывающего.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Отвязывает пользователя от проекта
      tags:
        - projects
  /projects/{projectId}/mirror-path:
    put:
      description: >-
        Абсолютный путь НА МАШИНЕ ПОЛЬЗОВАТЕЛЯ, куда локальный агент копирует
        работу (RN-515, ADR 0147), — папка ВНЕ смонтированной базы, в чём и
        состоит весь смысл существования зеркала. На проект и никогда не
        глобально: одно глобальное назначение помещало бы артефакты проекта B в
        папку проекта A, и пользователь узнал бы об этом по содержимому, а не из
        ошибки. `mirrorPath: null` ОЧИЩАЕТ его и выключает зеркало — ключ
        обязателен, его отсутствие даёт 400, потому что тело без поля было бы
        неотличимо от просьбы очистить. `null` — НОРМАЛЬНОЕ состояние проекта.
        `maintainer`, тот же минимум, что и у `execution-mode` и
        `projects-base`: этот маршрут говорит о пути в собственной файловой
        системе оператора. Он проверяет ТОЛЬКО ЛЕКСИЧЕСКУЮ форму и отклоняет с
        400 назначение внутри `workspacePath` или содержащее его (оба
        направления одной петли), а также вообще любое назначение у проекта
        `container`. Он никогда не касается диска: API не видит машину, на
        которой будет находиться назначение. Запись зеркала НЕ является
        proposed_action — это конфигурация, объявленная пользователем, а не
        агент, просящий действовать.
      operationId: ProjectsController_setMirrorPathRoute
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SetMirrorPathDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует или невидим для вызывающего.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Объявляет (или очищает) назначение зеркала проекта
      tags:
        - projects
  /projects/{projectId}/mirror-state:
    get:
      description: >-
        Телеметрия, которую локальный агент отправил по каналу после копирования
        работы в папку пользователя (ADR 0147, пункт 7), — никогда не журнал
        событий, потому что у раунда зеркала нет сессии, а
        `session_events.session_id` — `NOT NULL` (то же рассуждение, что сделало
        `rag_searches` таблицей). ТРИ ответа, которые никогда не сливаются в
        один (RN-088): `never` (ни один раунд не сообщался), `synced` (последний
        раунд скопировал — `filesCopied` может быть `0`, что значит «посмотрел,
        и копировать было нечего») и `failed`. Сбой никогда не стирает последнюю
        успешную синхронизацию, а успех никогда не стирает последнюю ошибку:
        какой из них ТЕКУЩИЙ, выясняется сравнением двух меток времени, поэтому
        экран может сказать «сбой с сегодняшнего дня, последняя хорошая копия
        была вчера, 412 файлов». `lastDestination` ЗАМОРОЖЕН — он расходится с
        `mirrorPath` после того, как кто-то меняет назначение, потому что
        разрешение передаётся в join и меняется только при переподключении
        runner (RN-516). `viewer`, тот же минимум, что и для чтения проекта,
        который уже несёт `mirrorPath`.
      operationId: ProjectsController_mirrorStateRoute
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectMirrorStateResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует или невидим для вызывающего.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Что сделал последний раунд зеркала (RN-517)
      tags:
        - projects
  /projects/{projectId}/model-binding:
    get:
      description: >-
        СЫРОЙ binding этой области действия — `null`, если проект только
        наследует от рабочего пространства.
      operationId: ModelBindingsController_getProjectBinding
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelBindingResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в области действия.
        '404':
          description: Область действия не найдена.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Читает binding модели проекта
      tags:
        - llm
    put:
      operationId: ModelBindingsController_setProjectBinding
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SetModelBindingDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelBindingResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в области действия.
        '404':
          description: Область действия не найдена.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Задаёт модель проекта
      tags:
        - llm
  /projects/{projectId}/model-bindings/resolved:
    get:
      description: >-
        Пакетная форма `GET .../agent-bindings/:agentSlug` и `GET
        .../area-bindings/:areaKey`: каждый запрошенный ключ получает РОВНО то
        значение, которое отвечает его маршрут для одного ключа (тот же каскад,
        те же origin, `null`, когда ни на одном уровне нет модели). Существует,
        чтобы вкладка Настройки читала 20 разрешённых binding одним запросом
        вместо 20 — лимит частоты действует на ПОЛЬЗОВАТЕЛЯ. Ключи через
        запятую, без дубликатов, а некорректный ключ или больше 64 ключей всего
        — это 400, никогда не молчаливое отбрасывание.
      operationId: ModelBindingsController_getResolvedBindings
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: agents
          required: false
          in: query
          description: Slug агентов через запятую.
          schema:
            example: criativo,po,dev-lead
            type: string
        - name: areas
          required: false
          in: query
          description: Ключи областей через запятую.
          schema:
            example: dev,qa,infra
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BindingsResolvidosEmLoteResponseDto'
        '400':
          description: Некорректный ключ или больше ключей, чем лимит пакета.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в области действия.
        '404':
          description: Область действия не найдена.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Определяет модель нескольких агентов и областей за одно чтение
      tags:
        - llm
  /projects/{projectId}/models:
    get:
      description: >-
        Категория `local` — модели Ollama, которые не тратят деньги; `cloud` —
        платные модели API. Модель, которую рабочее пространство не
        активировало, здесь не появляется, но остаётся в исторических затратах —
        чтобы увидеть её, используйте маршрут каталога. Список относится к
        рабочему пространству-ВЛАДЕЛЬЦУ проекта: одна и та же модель может быть
        включена в одном рабочем пространстве и выключена в соседнем (ADR 0049).
      operationId: ModelsController_list
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                properties:
                  local:
                    type: object
                    additionalProperties:
                      type: array
                      items:
                        $ref: '#/components/schemas/ModelResponseDto'
                  cloud:
                    type: object
                    additionalProperties:
                      type: array
                      items:
                        $ref: '#/components/schemas/ModelResponseDto'
                example:
                  local:
                    ollama: []
                  cloud:
                    anthropic: []
                    openai: []
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Перечисляет модели, активные В РАБОЧЕМ ПРОСТРАНСТВЕ ПРОЕКТА
      tags:
        - llm
  /projects/{projectId}/open-questions:
    get:
      description: >-
        Статус берётся из журнала событий: открытый — это
        `artifact.open_question` без `open_question.answered`. Сначала новые, не
        более 50, с `total`.
      operationId: OpenQuestionsController_list
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: status
          required: false
          in: query
          schema:
            default: open
            type: string
            enum:
              - open
              - answered
              - all
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OpenQuestionsListResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Перечисляет открытые вопросы агентов проекта (RN-858)
      tags:
        - backlog
  /projects/{projectId}/open-questions/{questionId}/answer:
    post:
      description: >-
        Отвечает только автор сессии, в которой родился вопрос. Пишет
        `decision_record` (актор: пользователь, с `questionId`) и
        `open_question.answered` и просит engine возобновить поставленный на
        паузу gate QA каждой затронутой задачи (RN-860), по мере возможности.
      operationId: OpenQuestionsController_answer
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: questionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AnswerOpenQuestionDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OpenQuestionResponseDto'
        '400':
          description: Нет ответа или принято предложение там, где его нет.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: 'Не автор сессии — тело с `reason: "so_o_autor_da_sessao"`.'
        '404':
          description: Вопрос не в этом проекте.
        '409':
          description: 'Уже отвечен — `reason: "pergunta_ja_respondida"`.'
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Отвечает на открытый вопрос (RN-858)
      tags:
        - backlog
  /projects/{projectId}/permissions:
    get:
      description: >-
        Это физический файл в корне рабочей области проекта, а не столбец базы
        данных.
      operationId: ProjectsController_getPermissions
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PermissionsFileResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует или невидим для вызывающего.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Читает permissions.json проекта
      tags:
        - projects
    put:
      description: >-
        Заменяет все три списка сразу. `deny` по-прежнему побеждает `allow` при
        оценке — разрешить через это что-то запрещённое невозможно.
      operationId: ProjectsController_setPermissions
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SetProjectPermissionsDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PermissionsFileResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует или невидим для вызывающего.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Перезаписывает permissions.json проекта
      tags:
        - projects
  /projects/{projectId}/personal-access-tokens:
    get:
      description: Никогда не включает сырой токен — только то, что уже было выпущено.
      operationId: PersonalAccessTokensController_listPats
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PersonalAccessTokenResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не найден.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Перечисляет собственные Personal Access Token этого проекта
      tags:
        - projects
    post:
      description: >-
        Сырой токен (`brb_...`) появляется только в ЭТОМ ответе — потом его
        нельзя восстановить, хранится только хеш. Используйте в
        `--token`/`BRABO_ACCOUNT_TOKEN` у `brabo-runner`.
      operationId: PersonalAccessTokensController_issuePat
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IssuePatRequestDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IssuePatResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не найден.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Выпускает Personal Access Token для локального runner
      tags:
        - projects
  /projects/{projectId}/personal-access-tokens/{tokenId}:
    delete:
      description: >-
        Идемпотентно — повторный отзыв не является ошибкой. Начиная с RN-685
        (ADR 0201) также РАЗРЫВАЕТ соединения runner, открытые с ЭТИМ токеном, и
        только их — ключ устройства того же пользователя остаётся, — и
        аннулирует его ещё не использованные тикеты. Недоступный engine НЕ
        приводит к сбою отзыва.
      operationId: PersonalAccessTokensController_revokePat
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: tokenId
          required: true
          in: path
          schema:
            type: string
      responses:
        '204':
          description: Токен отозван. Без тела.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не найден.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Отзывает собственный Personal Access Token
      tags:
        - projects
  /projects/{projectId}/personal-access-tokens/{tokenId}/admin:
    delete:
      description: >-
        Реагирование на инцидент — уволенный разработчик с утёкшим токеном
        (RN-427). Идемпотентно — повторный отзыв не является ошибкой. Начиная с
        RN-685 (ADR 0201) также РАЗРЫВАЕТ уже подключённый с этим токеном
        runner, и только его.
      operationId: PersonalAccessTokensController_revokePatAsMaintainer
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: tokenId
          required: true
          in: path
          schema:
            type: string
      responses:
        '204':
          description: Токен отозван. Без тела.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не найден.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Отзывает Personal Access Token ЛЮБОГО пользователя в проекте
      tags:
        - projects
  /projects/{projectId}/personal-access-tokens/all:
    get:
      description: >-
        Представление для `maintainer` для реагирования на инциденты (RN-427) —
        включает владельца каждого токена. Никогда не включает сырой токен.
      operationId: PersonalAccessTokensController_listAllPats
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PersonalAccessTokenAdminResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не найден.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Перечисляет ВСЕ Personal Access Token проекта, любого пользователя
      tags:
        - projects
  /projects/{projectId}/proficiency:
    get:
      description: >-
        По умолчанию СОБСТВЕННЫЙ профиль пользователя; агрегированное
        представление команды — только для тех, кто администрирует проект.
        Профиль компетенций — это данные О человеке, и наименее неожиданное
        значение по умолчанию — видеть свой.
      operationId: AnamneseController_proficiency
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ProficiencyProfileResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, событие или версия не существуют.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Возвращает профиль квалификации
      tags:
        - anamnesis
  /projects/{projectId}/proficiency/me:
    delete:
      description: >-
        Только СОБСТВЕННЫЙ профиль вызывающего, никогда чужой. Отказ от
        профилирования идёт вместе, потому что без него следующий раунд вывел бы
        всё заново, и удаление было бы косметическим. Намеренно `viewer`:
        профилирование охватывает всех участников, поэтому требование
        `developer` оставило бы профилированного viewer без возможности удалить
        свой.
      operationId: AnamneseController_deleteMine
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PerfilApagadoResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, событие или версия не существуют.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Удаляет собственный профиль и записывает отказ от профилирования
      tags:
        - anamnesis
  /projects/{projectId}/proficiency/me/opt-in:
    post:
      description: >-
        Отменяет отказ от профилирования. Профили снова начинают выводиться со
        следующего раунда.
      operationId: AnamneseController_optInMine
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PerfilOptInResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, событие или версия не существуют.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Снова включает собственное профилирование пользователя
      tags:
        - anamnesis
  /projects/{projectId}/psychologist/analyses:
    get:
      description: >-
        `costMicros` суммируется из `token_usage` раунда. Именно это делает
        разницу в стоимости между лёгкой и тяжёлой сортировкой ВИДИМОЙ, а не
        просто заявленной, — два уровня используют действительно разные модели.
      operationId: PsychologistController_analyses
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PsychologistAnalysisResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или гипотеза не существуют.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Перечисляет раунды анализа с уровнем и реальной стоимостью
      tags:
        - psychologist
  /projects/{projectId}/psychologist/status:
    get:
      description: >-
        Только чтение — без побочных эффектов. `enabled: false` означает, что
        пауза — решение ПРОДУКТА (не ошибка): существующие анализы и гипотезы ею
        не затрагиваются.
      operationId: PsychologistController_status
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PsychologistStatusResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или гипотеза не существуют.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Сообщает, может ли Психолог сегодня выполнить НОВЫЙ анализ
      tags:
        - psychologist
  /projects/{projectId}/rag/coverage:
    get:
      description: >-
        Реальный подсчёт (никогда не оценка) файлов `.md` в `docs`/`docs/adr`
        репозитория проекта против того, сколько из них имеют фрагмент, и сессий
        проекта против того, сколько имеют фрагмент. Не включает «N минут назад»
        для этих трёх областей — нет столбца метки времени индексации по
        области, и угаданное число лгало бы (то же правило, что ADR 0042 для
        оценки моделей). `local` (RN-455) — единственное исключение:
        `lastAttachedAt` — это реальный `MAX(created_at)` по этой области,
        потому что у загрузки из браузера нет состояния репозитория для
        сравнения.
      operationId: RagController_obterCobertura
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RagCoverageResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: 'Покрытие индекса: проиндексированные файлы/сессии против реального итога'
      tags:
        - rag
  /projects/{projectId}/rag/feedback:
    post:
      description: >-
        Единственный сигнал ИСТИНЫ в измерении RAG: задержка и частота
        деградации говорят, ВЫПОЛНИЛСЯ ли поиск, но никогда — был ли он ПРАВ. Та
        же роль, что и у `search`: кто может читать результат, тот может его
        оценивать. Повторный голос за тот же фрагмент того же поиска
        перезаписывает ваш собственный голос, а не добавляет второй (уникален на
        актора), поэтому метрика измеряет точность, а не энтузиазм.
      operationId: RagController_votar
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RagFeedbackRequestDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RagFeedbackResponseDto'
        '400':
          description: >-
            Неизвестный `searchId` для этого проекта или `chunkId`, которого не
            было среди результатов этого поиска, — голос без позиции был бы
            числом без смысла.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: >-
        Оценивает один результат одного поиска как полезный или нерелевантный
        (RN-480)
      tags:
        - rag
  /projects/{projectId}/rag/local:
    post:
      description: >-
        Текстовое содержимое, уже прочитанное браузером (`<input
        webkitdirectory>`), никогда не путь на хосте — в этом различии весь
        смысл ADR 0113. Идемпотентная полная перестройка области `local`: каждый
        вызов удаляет и пересоздаёт её из файлов, отправленных В ЭТОТ раз,
        поэтому повторная загрузка и есть механизм пересинхронизации. Отклоняет
        (400) при превышении лимитов числа файлов/общего объёма в байтах вместо
        молчаливой обрезки — это однократный жест пользователя с выбором папки
        перед ним, а не фоновое сканирование. Отдельный слишком большой файл или
        файл с нераспознанным расширением только пропускается и никогда не
        приводит к сбою пакета.
      operationId: RagController_anexarPastaLocal
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AttachLocalFolderRequestDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AttachLocalFolderResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: >-
        Прикрепляет локальную папку (загрузка из браузера) как справочный
        материал только для чтения (RN-455, ADR 0113)
      tags:
        - rag
  /projects/{projectId}/rag/reindex:
    post:
      description: >-
        «Переиндексировать сейчас» из панели RAG в Чате. Удаляет и пересоздаёт
        фрагменты трёх честных областей (RN-219) из ТЕКУЩЕГО состояния —
        автоматического наблюдателя на push/закрытие сессии нет (решение
        записано в ADR 0079/0080). Может занять время в проектах с большим
        числом сессий: выполняется один проход индексации на сессию, каждый из
        которых может вызывать провайдера эмбеддингов пакетно.
      operationId: RagController_reindexar
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReindexProjectResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: >-
        Переиндексирует docs/ADR/сессии проекта (идемпотентная полная
        перестройка)
      tags:
        - rag
  /projects/{projectId}/rag/search:
    post:
      description: >-
        Сочетает косинусное сходство (pgvector) с лексическим `ts_rank`
        (tsvector), каждое из независимого запроса, объединённые взвешенной
        суммой и отсечённые порогом (ADR 0080). `vectorAvailable: false`
        предупреждает, когда провайдер эмбеддингов не ответил и поиск выполнился
        только с лексическим сигналом, — он никогда не притворяется, что
        выполнил полный гибридный поиск.
      operationId: RagController_buscar
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HybridSearchRequestDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HybridSearchResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: >-
        Гибридный поиск (вектор + лексика) по проиндексированным фрагментам
        проекта
      tags:
        - rag
  /projects/{projectId}/run-instructions:
    get:
      description: >-
        RN-852 (ADR 0213): текущий `artifact.run_instructions`, выпускаемый,
        когда выполняется `open_infra_pr` с `run`. ОБЪЯВЛЕН агентом Infra и
        никогда не проверяется на машине читателя. `null`, когда его нет.
      operationId: BacklogController_runInstructions
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunInstructionsResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Возвращает, как запустить результат на вашей машине
      tags:
        - backlog
  /projects/{projectId}/runner-device-keys:
    get:
      description: >-
        Никто не отзовёт то, чего не видит (RN-519). Включает и уже ОТОЗВАННЫЕ —
        исчезновение строки заставило бы экран утверждать, что ключа никогда не
        было. Никогда не возвращает публичный JWK, а приватный api никогда не
        видела. Нулевой `lastUsedAt` — признак ОСИРОТЕВШЕГО ключа:
        зарегистрированного и ни разу не использованного ни одним runner.
        Начиная с RN-543 также включает МАШИННЫЕ ключи вызывающего (`especie:
        "maquina"`, `projectId` нулевой), которые обслуживают этот проект, не
        принадлежа ему, — без них в списке машинный ключ был бы невидим на любом
        экране.
      operationId: RunnerDeviceKeysController_listDeviceKeys
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/RunnerDeviceKeyListResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не найден.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Перечисляет собственные ключи устройств этого проекта
      tags:
        - projects
  /projects/{projectId}/runner-device-keys/{deviceKeyId}:
    delete:
      description: >-
        Идемпотентно — повторный отзыв не является ошибкой. Начиная с RN-520
        также РАЗРЫВАЕТ живое соединение: раньше отзыв только не давал получить
        НОВЫЙ тикет, а уже подключённый runner продолжал выполнять одобренные
        команды. Начиная с RN-685 (ADR 0201) целью является КЛЮЧ: разрываются
        только соединения, открытые с ним, — во всех проектах, если он машинный,
        — а другой runner того же пользователя, подключённый с PAT или другим
        ключом, остаётся. Его ещё не использованные тикеты аннулируются.
        Недоступный engine или отсутствие подключённого runner НЕ приводят к
        сбою отзыва.
      operationId: RunnerDeviceKeysController_revokeDeviceKey
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: deviceKeyId
          required: true
          in: path
          schema:
            type: string
      responses:
        '204':
          description: Ключ отозван. Без тела.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не найден.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Отзывает собственный ключ устройства
      tags:
        - projects
  /projects/{projectId}/runner-ticket:
    post:
      description: >-
        Аутентифицирует `connect/3` сокета Phoenix `/runner` (топик
        `terminal:<projectId>`, роль "runner") — это НЕ переиспользованный JWT,
        и этот маршрут сам принимает только Personal Access Token вместо JWT
        сессии (ADR 0105). TTL 30 с и однократное использование. Отклоняет с
        400, если проект не в режиме "runner" (RN-421): runner нечего
        обслуживать в проекте, код которого живёт в управляемом контейнере.
      operationId: RunnerTicketsController_runnerTicket
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunnerTicketResponseDto'
        '400':
          description: Проект не в режиме "runner".
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не найден.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Выпускает одноразовый тикет для подключения локального runner
      tags:
        - projects
  /projects/{projectId}/sessions:
    get:
      description: >-
        Каждый элемент несёт `technical`: `true` только для сессии, открытой при
        создании репозитория (связанной из `repo_bootstraps`).
      operationId: SessionsController_list
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/SessionListItemResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект или сессия не существуют.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Перечисляет сессии проекта
      tags:
        - sessions
    post:
      description: >-
        Сессия рождается в `created` и является контейнером для всего, что
        делают агенты: журнал событий, предложенные действия и бюджет токенов
        привязаны к ней. `kind` ОБЯЗАТЕЛЕН и записывается: `consultiva`
        (консультационная) только беседует, `criativa` (творческая) производит и
        является единственной, которая принимает `execution.activated`.
      operationId: SessionsController_create
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateSessionDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionResponseDto'
        '400':
          description: Тело без `kind` или со значением вне разрешённого списка.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект или сессия не существуют.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Открывает сессию в проекте
      tags:
        - sessions
  /projects/{projectId}/sessions/{sessionId}:
    get:
      operationId: SessionsController_get
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект или сессия не существуют.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Возвращает сессию по id
      tags:
        - sessions
    patch:
      description: >-
        Имя — это навигационная подпись, и оно НЕ заменяет хештег id: экраны
        составляют их вместе, а без имени деградируют до одного хештега. `null`
        или пустая строка очищают имя. Изменить `kind` нельзя: это намерение при
        создании, и изменение позже превратило бы его в состояние.
      operationId: SessionsController_rename
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RenameSessionDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionResponseDto'
        '400':
          description: Тело без `name` или имя длиннее лимита символов.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект или сессия не существуют.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Задаёт или очищает понятное имя сессии
      tags:
        - sessions
  /projects/{projectId}/sessions/{sessionId}/actions:
    get:
      description: >-
        Упорядочено по `seq`; используйте `nextCursor` как `afterSeq`.
        `latest=true` вместо этого получает ХВОСТ (самые новые `limit` действий,
        всё ещё по возрастанию) и игнорирует `afterSeq`. `status=pending`
        оставляет только действия, ожидающие решения, — с `latest` самые новые
        ожидающие, чтобы сессия с более чем 200 действиями не вытесняла новое
        ожидающее из окна (RN-637).
      operationId: ActionsController_list
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
        - name: afterSeq
          required: false
          in: query
          schema:
            example: 6
            type: string
        - name: limit
          required: false
          in: query
          schema:
            example: 50
            type: string
        - name: latest
          required: false
          in: query
          description: Получает хвост сессии; игнорирует `afterSeq`.
          schema:
            example: 'true'
            type: string
        - name: status
          required: false
          in: query
          description: Поддерживается только `pending`.
          schema:
            example: pending
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaginaDeAcoesResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или действие не найдены.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Постранично выдаёт предложенные действия сессии
      tags:
        - actions
    post:
      description: >-
        Действие оценивается по `permissions.json` проекта при создании.
        `resolvedPolicy` в ответе говорит, что решила политика: `auto_approve`
        выходит уже готовым к выполнению, `require_approval` ждёт решения
        человека, а `deny` рождается отклонённым. Пути, выполняющего действие в
        обход этого, нет.
      operationId: ActionsController_propose
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProposeActionDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProposedActionResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или действие не найдены.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Предлагает действие с внешним эффектом
      tags:
        - actions
  /projects/{projectId}/sessions/{sessionId}/actions/{actionId}/approve:
    post:
      description: >-
        Применяется только к этому действию. Решение остаётся в журнале событий
        с id того, кто решил.
      operationId: ActionsController_approve
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
        - name: actionId
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProposedActionResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или действие не найдены.
        '409':
          description: По действию уже принято решение, или оно уже выполнено.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Одобряет действие один раз
      tags:
        - actions
  /projects/{projectId}/sessions/{sessionId}/actions/{actionId}/approve_always:
    post:
      description: >-
        Помимо разрешения этого действия добавляет соответствующий шаблон в
        список `allow` проекта — будущие подходящие действия выходят
        `auto_approved` без вопросов. Шаблон, уже находящийся в `deny`, остаётся
        заблокированным. Одобрение и шаблон записываются в одной транзакции.
        Клик по действию, которое уже было ОДОБРЕНО, — идемпотентный успех
        (`desfecho: ja_aprovada`), шаблон записывается, только если его нет.
      operationId: ActionsController_approveAlways
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
        - name: actionId
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApproveAlwaysResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или действие не найдены.
        '409':
          description: >-
            Действие уже было ОТКЛОНЕНО (`reason: acao_ja_recusada`); шаблон не
            записывается.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Одобряет действие и записывает шаблон в permissions.json
      tags:
        - actions
  /projects/{projectId}/sessions/{sessionId}/actions/{actionId}/deny:
    post:
      description: Причина, если указана, остаётся в журнале событий вместе с решением.
      operationId: ActionsController_deny
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
        - name: actionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DenyActionDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProposedActionResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или действие не найдены.
        '409':
          description: По действию уже принято решение, или оно уже выполнено.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Отклоняет действие
      tags:
        - actions
  /projects/{projectId}/sessions/{sessionId}/agents/{agent}/cancel:
    post:
      description: >-
        Кнопка «Стоп» в поле ввода (RN-122): прерывает текущий вызов LLM в
        engine, обрывая соединение посреди потока, — это реально экономит
        токены, а не просто останавливает отрисовку на клиенте. Идемпотентно:
        без хода в процессе принимается без эффекта.
      operationId: AgentsController_cancel
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
        - name: agent
          required: true
          in: path
          description: Slug активного агента.
          schema:
            example: po
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OkResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или handoff не найдены.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Отменяет текущий ход активного агента
      tags:
        - agents
  /projects/{projectId}/sessions/{sessionId}/agents/{agent}/message:
    post:
      description: >-
        Ответ — это лишь подтверждение, и он возвращается при ПРИНЯТИИ — до
        окончания хода агента (ADR 0163). То, что отвечает агент, приходит через
        журнал событий и канал сессии, а не через этот вызов. Если агент посреди
        хода, сообщение НЕ отклоняется: оно попадает в очередь агента (`entrega:
        "enfileirada"`) и читается вместе с другими поставленными в очередь за
        один ход, когда текущий закончится; пока оно ждёт, его можно отменить
        (RN-673, ADR 0191).
      operationId: AgentsController_message
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
        - name: agent
          required: true
          in: path
          description: >-
            Slug разговорного агента, который читает сообщение: criativo, po,
            arquiteto, dev-lead, ux-designer или staff. Любой другой slug —
            включая infra — отклоняется с 422; оно никогда не доставляется
            агенту по умолчанию (RN-584).
          schema:
            example: po
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendAgentMessageDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MensagemAoAgenteResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или handoff не найдены.
        '409':
          description: >-
            Агент не активен в этой сессии; или он ждёт решения по плану
            выполнения; или в его очереди уже 10 сообщений
            (`fila_de_mensagens_cheia`) — сообщение записано, но агент его НЕ
            прочитал (ADR 0163, RN-673).
        '422':
          description: >-
            Агент не принимает сообщения чата (Infra Lead работает через
            предложения, а любой slug без своего условия в engine отклоняется по
            имени) — сообщение записано, но НИ ОДИН агент его не прочитал
            (RN-584).
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Отправляет сообщение активному агенту
      tags:
        - agents
  /projects/{projectId}/sessions/{sessionId}/agents/{agent}/messages/{messageId}/cancel:
    post:
      description: >-
        Сообщение, отправленное, пока агент посреди хода, ждёт в очереди и
        читается, когда ход закончится (RN-673). Пока оно ждёт, ОТПРАВИВШИЙ его
        может его отменить: engine удаляет его из очереди и записывает
        `chat.message_cancelled`; модели оно никогда не показывается. Чужое
        сообщение — 403.
      operationId: AgentsController_cancelQueued
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
        - name: agent
          required: true
          in: path
          description: Slug агента, в очереди которого находится сообщение.
          schema:
            example: po
            type: string
        - name: messageId
          required: true
          in: path
          description: >-
            Id события `chat.message` (`mensagemId`, который вернул маршрут
            сообщения).
          schema:
            example: 01JC4Z0000EVENTO000000000001
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OkResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или handoff не найдены.
        '409':
          description: >-
            Сообщение больше не в очереди — агент его уже прочитал или оно уже
            отменено; или сессия закрыта (RN-581).
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Отменяет одно сообщение, ожидающее в очереди агента
      tags:
        - agents
  /projects/{projectId}/sessions/{sessionId}/agents/{agent}/resume:
    post:
      description: >-
        Human gesture, with no cap: the automatic resume happens once at boot
        (ADR 0226) and this is the button for the rest. The reply is the ACCEPT
        (the turn goes up in the engine); progress arrives on the session
        channel. Requires `developer`.
      operationId: AgentResumeController_resume
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
        - name: agent
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OkResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Роль ниже `developer` в проекте.
        '404':
          description: Session does not exist.
        '409':
          description: >-
            Nothing to resume — `reason: turno_nao_interrompido` (the agent has
            no interrupted turn, or one is already running) or
            `agente_sem_retomada` (the agent cannot resume).
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Resumes the turn an engine restart interrupted
      tags:
        - sessions
  /projects/{projectId}/sessions/{sessionId}/agents/{agent}/start:
    post:
      description: >-
        Прямой командой можно запустить только Креатив. Любой другой агент
        требует ПРИНЯТОГО handoff, указывающего на него, — правило живёт в
        домене, а не здесь, и попытка обойти его возвращает 409.
      operationId: AgentsController_start
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
        - name: agent
          required: true
          in: path
          description: Slug агента.
          schema:
            example: criativo
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgenteAtivadoResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или handoff не найдены.
        '409':
          description: Агент без принятого handoff или уже активен в сессии.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Запускает агента в сессии
      tags:
        - agents
  /projects/{projectId}/sessions/{sessionId}/agents/{agent}/structured-question/{questionSetId}/answer:
    post:
      description: >-
        Записывает `chat.structured_question_answered` и повторно отправляет
        ответы агенту как обычное сообщение. На набор вопросов можно ответить
        только один раз. Возвращается при ПРИНЯТИИ, до окончания хода агента
        (ADR 0163): ход продолжается в engine, а его ход, завершение и сбои
        приходят через канал сессии и журнал событий (`agent.status`,
        `agent.response`, `agent.error`) — никогда через этот ответ.
      operationId: AgentsController_submitStructuredQuestionAnswer
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
        - name: agent
          required: true
          in: path
          description: Slug агента, который задал вопросы (и прочитает ответ).
          schema:
            example: criativo
            type: string
        - name: questionSetId
          required: true
          in: path
          description: Id отвеченного события `chat.structured_question`.
          schema:
            example: 01JC4Z0000EVENTO000000000001
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AnswerStructuredQuestionDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OkResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или handoff не найдены.
        '409':
          description: >-
            На этот набор вопросов уже ответили; или агент всё ещё посреди хода
            и не прочитал ответы (ADR 0163).
        '422':
          description: >-
            Агент, который спросил, не принимает сообщения чата — ответы
            записаны, но НИ ОДИН агент их не прочитал (RN-584).
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Отвечает на набор структурированных вопросов агента
      tags:
        - agents
  /projects/{projectId}/sessions/{sessionId}/agents/{agentId}/rearm:
    post:
      description: >-
        Сбрасывает счётчик последовательных заблокированных задач (RN-047) и
        возвращает агента к попыткам забрать работу. Это единственный выход из
        `idle_tripped` — автоматической разблокировки нет.
      operationId: ExecutionController_rearm
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
        - name: agentId
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OkResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или задача не найдены.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Перезапускает dev agent, сработавший предохранитель
      tags:
        - execution
  /projects/{projectId}/sessions/{sessionId}/agents/arquiteto/handoff-infra:
    post:
      description: >-
        Выделенный endpoint вместо повторного использования `readiness`,
        принадлежащего Креативу: это две разные вехи сессии, и их смешение
        сделало бы журнал событий неоднозначным. Возвращается при ПРИНЯТИИ, до
        окончания хода агента (ADR 0163): ход продолжается в engine, а его ход,
        завершение и сбои приходят через канал сессии и журнал событий
        (`agent.status`, `agent.response`, `agent.error`) — никогда через этот
        ответ. Handoff к Dev Lead по-прежнему идёт ПОСЛЕ handoff к Infra: engine
        удерживает его, пока не закончится завершающий ход. Идемпотентно (ADR
        0182, RN-635): цель, у которой уже есть ожидающее предложение или
        которая активна в проекте, повторно не запускается, а если обе такие —
        ничего не записывается.
      operationId: AgentsController_handoffInfra
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConfirmacaoDeArquiteturaResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или handoff не найдены.
        '409':
          description: >-
            Архитектор всё ещё посреди хода — подтверждение записано, но
            завершающий ход не начался (ADR 0163).
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Подтверждает готовность архитектуры и предлагает handoff к Infra
      tags:
        - agents
  /projects/{projectId}/sessions/{sessionId}/agents/criativo/validate-necessity:
    post:
      description: >-
        Записывает `necessity.validated`. Требует, чтобы Креатив уже
        консолидировал `product_brief` в этой сессии (RN-406), — без него
        отклоняется: подтверждать нечего. Начиная с ADR 0185 (RN-657) клик «Я
        готов — потребность подтверждена» (`POST .../readiness`) сам записывает
        это событие, и веб больше не вызывает этот маршрут; он остаётся для
        сессий, чей клик готовности появился раньше этого ADR.
      operationId: AgentsController_validateNecessityHandoff
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OkResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или handoff не найдены.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Подтверждает, что бизнес-потребность Креатива подтверждена
      tags:
        - agents
  /projects/{projectId}/sessions/{sessionId}/budget:
    get:
      description: Именно это питает живой счётчик токенов на экране сессии.
      operationId: BudgetsController_getSessionBudget
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BudgetResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект или сессия не найдены.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Читает бюджет сессии и накопленные расходы
      tags:
        - llm
    put:
      description: То же преобразование долларов в микро-USD, что и у бюджета проекта.
      operationId: BudgetsController_setSessionBudget
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpsertBudgetDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BudgetResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект или сессия не найдены.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Задаёт лимит расходов сессии
      tags:
        - llm
  /projects/{projectId}/sessions/{sessionId}/chat:
    post:
      description: >-
        Server-Sent Events. Кадры `delta` несут инкрементальный текст, а `done`
        закрывает поток с учётом токенов и стоимости. Кадр `metering_failed`
        означает, что ОТВЕТ ушёл, но стоимость не была учтена, — сбой
        проявляется, а не исчезает. Если бюджет превышен при `policy=block`,
        поток несёт `error` и ни одного `delta`. Этот маршрут НЕ несёт агента:
        он отправляет только текст привязанной модели, без истории и без
        системного промпта. В сессии `consultiva`, где ни один агент не
        активировался, он отклоняется до каких-либо эффектов (RN-682) — вместо
        этого говорите с агентом через `.../agents/:agent/message`.
      operationId: ChatController_chat
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendChatMessageDto'
      responses:
        '200':
          description: Поток кадров до `done` или `error`.
          content:
            text/event-stream:
              schema:
                $ref: '#/components/schemas/ChatSseEventResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект или сессия не найдены.
        '422':
          description: >-
            `destinatario_ausente`: сессия `consultiva` без агента — у сообщения
            нет получателя. Ничего не записано, и ни одна модель не вызывалась
            (RN-682).
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Беседует с моделью сессии, с потоковым ответом
      tags:
        - llm
  /projects/{projectId}/sessions/{sessionId}/context-summary:
    get:
      description: >-
        Бизнес-правила, записи решений и артефакты по агентам, подсчитанные по
        ВСЕМУ журналу сессии (экран читает хвост из 200 событий), плюс файлы,
        затронутые `git_commit`/`git_push` в этой сессии и в сессии выполнения,
        которую активировал её одобренный план Dev Lead (RN-828). Начиная с
        RN-844 также возвращает ЭЛЕМЕНТЫ правил и решений (последние 50 каждого;
        итоги остаются в `regras`/`decisoes`) и файлы PR, открытых в этих
        сессиях, прочитанные из того же diff, что читает вкладка PR, не более
        чем для 20 PR за вызов; `prsNaoLidas` говорит, сколько было пропущено
        или завершилось сбоем. Только чтение; новой таблицы нет.
      operationId: SessionsController_contextSummary
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionContextSummaryResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект или сессия не существуют.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Подсчитывает всю сессию для панели контекста
      tags:
        - sessions
  /projects/{projectId}/sessions/{sessionId}/events:
    get:
      description: >-
        Журнал НЕИЗМЕНЯЕМ и упорядочен по `seq`. Обычная пагинация
        инкрементальна: передавайте `nextCursor` предыдущего ответа как
        `afterSeq`. `latest=true` делает обратное — получает ХВОСТ и игнорирует
        `afterSeq`, для экрана, который открывается с конца, а не
        восстанавливает всю сессию с начала (ADR 0021).
      operationId: SessionsController_listEvents
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
        - name: afterSeq
          required: false
          in: query
          description: Возвращает события с `seq` больше этого.
          schema:
            example: 40
            type: string
        - name: limit
          required: false
          in: query
          schema:
            example: 50
            type: string
        - name: latest
          required: false
          in: query
          description: Получает хвост журнала; игнорирует `afterSeq`.
          schema:
            example: 'true'
            type: string
        - name: actionId
          required: false
          in: query
          description: >-
            Только события, у которых `payload.actionId` — это действие,
            например его `proposed_action.created`, несущий `reason` политики и
            `scopeRoot`, которые строка действия не хранит (RN-614). Сочетается
            с другими параметрами; `seq` на странице больше не непрерывен,
            поэтому вызывающий, фильтрующий по действию, не должен выводить из
            него число пропущенных. Пустое значение — это `400`.
          schema:
            example: 01JC4Z0000ACAO00000000000001
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaginaDeEventosResponseDto'
        '400':
          description: '`actionId` присутствует, но пуст.'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект или сессия не существуют.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Постранично выдаёт журнал событий сессии
      tags:
        - sessions
    post:
      description: >-
        Только добавление: `seq` присваивается сервером, и ничто в журнале потом
        не обновляется и не удаляется. Endpoint для редактирования нет, по
        замыслу.
      operationId: SessionsController_appendEvent
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AppendSessionEventDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionEventResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект или сессия не существуют.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Добавляет событие в журнал сессии
      tags:
        - sessions
  /projects/{projectId}/sessions/{sessionId}/events/{eventId}:
    get:
      description: >-
        Существует, чтобы доказательства гипотез Психолога могли дойти до
        процитированного события независимо от того, на какую страницу оно
        попало, и от фильтров ленты.
      operationId: SessionsController_getEvent
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
        - name: eventId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionEventResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект или сессия не существуют.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Возвращает событие журнала по id
      tags:
        - sessions
  /projects/{projectId}/sessions/{sessionId}/execution/parallelize:
    post:
      description: >-
        Проходит через ЛИМИТ области dev (RN-083): ниже него агент стартует
        сразу, в собственном worktree; выше — ответ является `proposed_action`
        типа `parallelize`, ожидающим вашего решения, и НИЧЕГО не стартует, пока
        вы не решите. Значение лимита настраивается для каждого lead в
        Настройках.
      operationId: ExecutionController_parallelize
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AcceptParallelizationDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PedidoDeParalelismoResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или задача не найдены.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Запрашивает ещё одного dev agent для модуля
      tags:
        - execution
  /projects/{projectId}/sessions/{sessionId}/handoffs:
    get:
      operationId: AgentsController_handoffs
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/HandoffResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или handoff не найдены.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Перечисляет handoff между агентами в сессии
      tags:
        - agents
    post:
      description: >-
        Рождается как `offered`, в точности как собственный `offer_handoff`
        агента, — единственная разница в том, кто решил. `toAgent` должен быть в
        адресуемом каталоге (lead области или агент без области); субагент или
        неизвестный slug отклоняется с 400. Не более одного ожидающего
        предложения на (проект, цель): см. `desfecho` (ADR 0182, RN-635).
      operationId: AgentsController_requestManual
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RequestManualHandoffDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OfertaDeHandoffResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или handoff не найдены.
        '409':
          description: >-
            `agente_ja_ativo` (ADR 0182, RN-635): цель уже активна в незакрытой
            сессии проекта — предложение не создаётся.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Вручную предлагает handoff выбранному агенту
      tags:
        - agents
  /projects/{projectId}/sessions/{sessionId}/handoffs/{handoffId}/accept:
    post:
      description: >-
        Одно-единственное: переход в `accepted` И запуск агента назначения. Это
        обычный путь активации для любого агента, кроме Креатива.
      operationId: AgentsController_accept
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
        - name: handoffId
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HandoffResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или handoff не найдены.
        '409':
          description: Handoff не в состоянии `offered`.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Принимает handoff и активирует агента назначения
      tags:
        - agents
  /projects/{projectId}/sessions/{sessionId}/model-binding:
    get:
      description: >-
        Возвращает binding, РАЗРЕШЁННЫЙ каскадом, а не сырой binding сессии:
        сессия без собственного binding использует binding проекта, и ответ
        `null` здесь был бы неверным ответом на верный вопрос. Поле `origin`
        говорит, из какой области действия пришло значение.


        `agentId` необязателен и является агентом, ДЕЙСТВИТЕЛЬНО активным в
        сессии прямо сейчас (тем же, что использует `RunLlmTurnUseCase` для
        выполнения хода), — без него каскад видит только сессия→проект→рабочее
        пространство (плюс фиксированный откат к Креативу) и никогда не отражает
        PO/Архитектора/Dev Lead/область после handoff.
      operationId: ModelBindingsController_getSessionBinding
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
        - name: agentId
          required: false
          in: query
          description: >-
            Агент, активный в этой сессии в данный момент (например, "po",
            "arquiteto", "dev-lead").
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResolvedBindingResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в области действия.
        '404':
          description: Область действия не найдена.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Определяет, какую модель использует сессия и откуда она взялась
      tags:
        - llm
    put:
      description: Побеждает всё остальное в каскаде, пока живёт сессия.
      operationId: ModelBindingsController_setSessionBinding
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SetModelBindingDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelBindingResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в области действия.
        '404':
          description: Область действия не найдена.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Закрепляет модель для этой сессии
      tags:
        - llm
  /projects/{projectId}/sessions/{sessionId}/psychologist/reanalyze:
    post:
      description: >-
        Требует `maintainer`, а не `developer`, потому что снова запускает
        ToolLoop и ТРАТИТ реальный бюджет — в отличие от принятия или
        отклонения, которые лишь меняют локальное состояние. Предыдущий анализ
        помечается как замещённый.
      operationId: PsychologistController_reanalyze
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OkResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или гипотеза не существуют.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
        '503':
          description: >-
            Психолог глобально отключён по решению пользователя (не ошибка) —
            тело с `reason: "psychologist_disabled"`.
      security:
        - bearer: []
      summary: Запускает повторный анализ сессии
      tags:
        - psychologist
  /projects/{projectId}/sessions/{sessionId}/readiness:
    post:
      description: >-
        Это кнопка, запускающая `product_brief` и handoff к PO. Записывает
        `readiness.confirmed` в журнал событий. Возвращается при ПРИНЯТИИ, до
        окончания хода агента (ADR 0163): ход продолжается в engine, а его ход,
        завершение и сбои приходят через канал сессии и журнал событий
        (`agent.status`, `agent.response`, `agent.error`) — никогда через этот
        ответ.
      operationId: AgentsController_readiness
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OkResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или handoff не найдены.
        '409':
          description: >-
            Креатив всё ещё посреди хода — подтверждение записано, но brief не
            начался (ADR 0163).
        '422':
          description: >-
            В этом разговоре не было собрано ни одного бизнес-правила — пока
            нечего консолидировать в brief (ADR 0163).
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Подтверждает, что сессия исследования с Креативом завершена
      tags:
        - agents
  /projects/{projectId}/sessions/{sessionId}/reopen:
    post:
      description: >-
        Повторно открываются только `closed`/`closed_abnormally`, и только в
        `active` — `closing` никогда не возвращается (ADR 0183). Сессия
        сохраняет журнал событий, артефакты, отвеченные вопросы и handoff;
        `kind` не затрагивается. НОВОЕ событие `session.reopened` записывает
        прежние `closedAt` и `terminationReason`, которые строка очищает.
        Сессия, несущая `execution.activated`, отклоняется
        (`sessao_com_execucao`): откройте новую сессию и активируйте выполнение
        там. Без ограничения по времени. Требует `developer` (ADR 0184).
      operationId: SessionsController_reopen
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект или сессия не существуют.
        '409':
          description: >-
            Сессия не закрыта (`reason: sessao_nao_encerrada`) или активировала
            выполнение (`reason: sessao_com_execucao`).
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Повторно открывает закрытую сессию, сохраняя всё, что в ней было
      tags:
        - sessions
  /projects/{projectId}/sessions/{sessionId}/response-language:
    get:
      description: >-
        Определяется для ВЫЗЫВАЮЩЕГО, никогда для сессии: переопределение сессии
        > выбор в Учётной записи > обнаруженный и подтверждённый > язык
        интерфейса (RN-618). Возвращает победителя, его происхождение и каждое
        звено цепочки — и, когда ваши недавние сообщения указывают на другой
        язык и экран должен о нём спросить, `detectionQuestion` (RN-624).
      operationId: SessionResponseLanguageController_get
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionResponseLanguageResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект или сессия не существуют.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Язык, на котором агенты отвечают ВАМ в этой сессии
      tags:
        - sessions
    put:
      description: >-
        Только для вызывающего, только в этой сессии (RN-618) — другие участники
        и ваш выбор в Учётной записи не затрагиваются. `null` возвращает к
        наследованию из Учётной записи.
      operationId: SessionResponseLanguageController_set
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SetSessionResponseLanguageDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionResponseLanguageResponseDto'
        '400':
          description: >-
            Тело без `language` или код, не являющийся распознанным языком
            BCP-47.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект или сессия не существуют.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Закрепляет (или освобождает) ВАШ язык ответов в этой сессии
      tags:
        - sessions
  /projects/{projectId}/sessions/{sessionId}/socket-ticket:
    post:
      description: >-
        Тикет аутентифицирует сокет Phoenix сессии `session:<id>` при
        `connect/3` в engine — это НЕ переиспользованный JWT. TTL 30 с и
        однократное использование: каждое переподключение (включая
        автоматические) запрашивает новый тикет. `scope: "heartbeat"` требует
        роли `viewer`; `scope: "terminal"` требует `developer`, ту же
        минимальную роль, что и у действий терминала.
      operationId: SessionsController_issueSocketTicket
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateSocketTicketDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SocketTicketResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль для запрошенной области действия.
        '404':
          description: Проект или сессия не существуют.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Выпускает непрозрачный одноразовый тикет для сокета сессии
      tags:
        - sessions
  /projects/{projectId}/sessions/{sessionId}/tasks/{taskId}/unblock:
    post:
      description: >-
        Сбрасывает счётчик исправлений gate и возвращает задачу агенту. Это
        ручной выход для лимита исправлений — автоматической разблокировки нет.
      operationId: ExecutionController_unblock
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
        - name: taskId
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OkResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект, сессия или задача не найдены.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Разблокирует заблокированную задачу
      tags:
        - execution
  /projects/{projectId}/sessions/{sessionId}/token-usage:
    get:
      description: >-
        Данные всегда были в `token_usage`, но без агрегации и маршрута панель
        команды не могла показать стоимость по карточке. Требуется та же роль,
        что и для бюджета сессии.
      operationId: BudgetsController_getSessionTokenUsage
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/SessionAgentTokenUsageResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект или сессия не найдены.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Разбивает расходы сессии по агентам
      tags:
        - llm
  /projects/{projectId}/sessions/{sessionId}/transition:
    post:
      description: >-
        Переход проверяется машиной состояний домена, а не клиентом: created →
        active → closing → closed | closed_abnormally. Недопустимый переход —
        409.
      operationId: SessionsController_transition
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TransitionSessionDto'
      responses:
        '201':
          description: >-
            Возвращает 201, а не 200, потому что обработчик — `@Post` без
            `@HttpCode`, это поведение Nest по умолчанию. Задокументировано как
            есть, а не изменено: семантика спорна, поведение не дефектно, а
            табличный тест падает при любом расхождении между ними.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект или сессия не существуют.
        '409':
          description: Переход из текущего состояния не разрешён.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Переводит сессию в другое состояние
      tags:
        - sessions
  /projects/{projectId}/spend/me:
    get:
      description: >-
        Аудитория УЧАСТНИКА. Только собственные строки вызывающего — актор
        берётся из аутентифицированного токена, и параметра для его подмены нет.
        В токенах и ОЦЕНОЧНОЙ стоимости, без разбивки по провайдеру или учётным
        данным: ключ, который работал, принадлежит владельцу (RN-058), и счёт по
        нему — его (RN-060). Ось провайдера появилась в отчёте владельца (ADR
        0076) и остаётся недоступной отсюда — на этом маршруте нет параметра
        измерения, а тип репозитория отклоняет такую комбинацию (RN-187). Агент
        не учитывается: `token_usage` записывает, кто потратил, а не кто заказал
        расход.
      operationId: SpendController_getMySpend
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: dias
          required: false
          in: query
          description: Скользящее окно. По умолчанию 30, максимум 180.
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MySpendResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Моё собственное потребление в этом проекте, по сессиям и по дням
      tags:
        - llm
  /projects/{projectId}/spend/team:
    get:
      description: >-
        The MEMBER's view of the PROJECT (RN-953, ADR 0233, revising 0063):
        total and cost per agent/area, NEVER provider or credential — the route
        has no dimension parameter and never asks for `provider`.
      operationId: SpendController_getProjectSpendTeam
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: dias
          required: false
          in: query
          description: Скользящее окно. По умолчанию 30, максимум 180.
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectSpendTeamResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: The project's total spend and cost by agent and area
      tags:
        - llm
  /projects/{projectId}/stories/{storyId}/archive:
    post:
      description: >-
        История уходит из backlog, покрытия, очереди продвижения, плана Dev Lead
        и claim — а вместе с ней и её задачи. Ничего не удаляется: строка
        остаётся с `archivedAt`, а `backlog.story_archived` записывает, кто,
        почему и какие задачи. Только история `draft` без задачи в работе.
      operationId: BacklogController_archive
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: storyId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ArchiveStoryDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StoryResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует.
        '409':
          description: >-
            Именованный отказ (`reason`): `historia_arquivada`,
            `historia_nao_draft` или `historia_com_tarefa_em_execucao`.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Архивирует черновую историю (RN-727)
      tags:
        - backlog
  /projects/{projectId}/stories/{storyId}/return:
    post:
      description: >-
        История уходит из очереди предложений, а причина становится сообщением,
        закреплённым в сессии PO, — тот же шаблон, что и при возврате gate к
        dev. Если PO этой сессии больше не запущен, возврат всё равно
        записывается: это решение пользователя, и оно не зависит от того,
        слушает ли агент.
      operationId: BacklogController_return
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: storyId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReturnStoryDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OkResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Возвращает отклонённую историю PO с указанием причины
      tags:
        - backlog
  /projects/{projectId}/stories/{storyId}/title:
    patch:
      description: >-
        Только история `draft` без задачи в работе (`in_progress`/`in_review`).
        Пишет `backlog.story_updated` с прежним и новым заголовком. Та же роль,
        что и у других записей backlog (продвижение/возврат).
      operationId: BacklogController_updateTitle
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: storyId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateStoryTitleDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StoryResponseDto'
        '400':
          description: Пустой заголовок или длиннее 200 символов.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует.
        '409':
          description: >-
            Именованный отказ (`reason`): `historia_arquivada`,
            `historia_nao_draft` или `historia_com_tarefa_em_execucao`.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Исправляет заголовок черновой истории (RN-727)
      tags:
        - backlog
  /projects/{projectId}/stories/promote:
    post:
      description: >-
        Шаг человека, который режим `manual` (по умолчанию начиная с Фазы 12c)
        возвращает пользователю: до продвижения НИ ОДНА задача истории не может
        быть взята ни одним dev agent. Продвижение высвобождает пакет задач
        сразу и будит простаивающих агентов модуля (Фаза 12b). Пакет НЕ является
        «всё или ничего» — то, что не прошло, возвращается как `failed` с
        причиной, а то, что прошло, продвигается.
      operationId: BacklogController_promote
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PromoteStoriesDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PromoteStoriesResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не существует.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Продвигает предложенные PO истории в `ready`
      tags:
        - backlog
  /projects/{projectId}/tasks/{taskId}/gates/{gate}/resume:
    post:
      description: >-
        Цикл gate, застрявший более чем на 2 ч, паркуется, а не перезапускается
        сам (ADR 0207). Это жест человека, который его возобновляет.
      operationId: GateResumeController_resume
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: taskId
          required: true
          in: path
          schema:
            type: string
        - name: gate
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OkResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Роль ниже `developer` в проекте.
        '409':
          description: 'Цикл не припаркован — тело с `reason: "gate_nao_estacionado"`.'
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Возобновляет припаркованный цикл gate
      tags:
        - gates
  /projects/{projectId}/terminal-ticket:
    post:
      description: >-
        Тот же сокет `/runner`, тот же топик, роль "terminal" — тот, кто ВИДИТ
        терминал (сама команда действительно выполняется, только если подключён
        runner). Действует для любого режима проекта.
      operationId: RunnerTicketsController_terminalTicket
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunnerTicketResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в проекте.
        '404':
          description: Проект не найден.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Выпускает одноразовый тикет для вкладки Терминал в вебе
      tags:
        - projects
  /runner-releases/binary:
    get:
      description: >-
        Прокси GitHub Releases — `platform` принимает только linux-x64,
        linux-arm64, darwin-arm64. Без аутентификации: бинарный файл не секрет.
        Байты сверяются с `checksums.txt` той же Release до того, как что-либо
        будет отдано (ADR 0149, RN-525); маршрут НЕ проверяет подпись манифеста
        — это целостность, а не происхождение, и подпись проверяет `install.sh`.
      operationId: RunnerReleasesController_binary
      parameters: []
      responses:
        '200':
          description: >-
            Бинарный файл runner потоком, с sha256, уже сверенным с манифестом.
            Не JSON: `Content-Type: application/octet-stream`. Заголовок
            `Content-Digest` повторяет сверенный хеш (RFC 9530).
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        '502':
          description: >-
            Именованный отказ — никогда байты с предупреждением. Тело содержит
            `motivo`: `plataforma_nao_publicada`, `release_sem_manifesto`,
            `manifesto_nao_cobre_a_plataforma`, `manifesto_ilegivel`,
            `download_falhou` или `hash_divergente`.
      summary: >-
        Скачивает автономный бинарный файл локального runner для запрошенной
        платформы
      tags:
        - infrastructure
  /runner/llm-credentials:
    post:
      description: >-
        Authenticated by a MACHINE device key (never a session JWT, never a
        project-bound credential). Same envelope encryption as `POST
        /users/me/credentials`. 409 `credencial_existente` when the user already
        has this provider and `substituir` is not `true`. The key is never
        logged nor returned.
      operationId: RunnerLlmCredentialsController_execute
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ImportLlmCredentialDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ImportLlmCredentialResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Credential bound to a project.
        '409':
          description: Code `credencial_existente`.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Stores an LLM API key imported by the local runner
      tags:
        - credentials
  /runner/projects:
    get:
      description: >-
        Аутентифицируется МАШИННЫМ ключом устройства (ADR 0154) — НИКОГДА JWT
        сессии и никогда учётными данными, привязанными к проекту, на которые
        отвечает 403. Возвращает проекты в `execution_mode: "runner"`, в которых
        владелец ключа достигает как минимум `developer` (тот же минимум, что и
        у `POST .../runner-ticket`), с именем папки и состоянием проверки
        каждого. Агент спрашивает, а не сканирует диск: база машины принадлежит
        пользователю и может содержать папки, не являющиеся проектами.
      operationId: RunnerProjectsController_listRunnerProjects
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/RunnerProjectResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: >-
            Предъявленные учётные данные привязаны к проекту — этот маршрут
            требует машинного ключа устройства.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: >-
        Перечисляет проекты в режиме "runner", которые обслуживает этот
        локальный агент
      tags:
        - projects
  /users/me/credentials:
    get:
      description: >-
        Роль не требуется: учётные данные относятся к тому, кто вызвал, а не к
        рабочему пространству или проекту. Охватывает ключи LLM и токены git в
        одном списке.
      operationId: CredentialsController_list
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/UserCredentialResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Перечисляет собственные учётные данные текущего пользователя
      tags:
        - credentials
    post:
      description: >-
        Upsert по (пользователь, провайдер). Ключ шифруется envelope-шифрованием
        до того, как коснётся базы данных, а ответ несёт только ПРОЕКЦИЮ —
        никогда сам секрет, даже зашифрованный.
      operationId: CredentialsController_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpsertCredentialDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserCredentialResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Регистрирует или заменяет API-ключ провайдера
      tags:
        - credentials
  /users/me/credentials/{provider}:
    delete:
      description: 'Идемпотентно: удаление несуществующего тоже отвечает `{ ok: true }`.'
      operationId: CredentialsController_remove
      parameters:
        - name: provider
          required: true
          in: path
          schema:
            example: anthropic
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OkResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Удаляет учётные данные провайдера
      tags:
        - credentials
  /users/me/credentials/{provider}/test:
    post:
      description: >-
        Проверка — явное действие, отдельное от регистрации (ADR 0050): api
        расшифровывает секрет, вызывает провайдера и возвращает ТОЛЬКО вердикт —
        ключ никогда не возвращается, даже частично. Отвечает 200 для всех трёх
        исходов, потому что запрос был обработан; `refused` — это исход, а не
        ошибка протокола. 404, когда нет учётных данных для (пользователь,
        провайдер), — тогда и проверять нечего.
      operationId: CredentialsController_test
      parameters:
        - name: provider
          required: true
          in: path
          schema:
            example: openrouter
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CredentialTestResultResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '404':
          description: Нет учётных данных для этого провайдера.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Проверяет УЖЕ сохранённые учётные данные у провайдера
      tags:
        - credentials
  /users/me/git-credentials:
    post:
      description: >-
        Собственный путь, а не `users/me/credentials`, потому что тело другое
        (`token`, а перечисление провайдеров — `github`/`gitlab`). Токен
        шифруется и сохраняется без предварительной проверки (ADR 0050); чтобы
        проверить его — `POST /users/me/credentials/{provider}/test`. Чтение,
        удаление и проверка переиспользуют маршруты `users/me/credentials` — это
        та же таблица.
      operationId: GitCredentialsController_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegisterGitCredentialDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserCredentialResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Регистрирует токен git (зашифрованный, без проверки)
      tags:
        - credentials
  /users/me/machine-device-keys:
    get:
      description: >-
        На уровне учётной записи, без проекта в пути (RN-611): установка в одну
        строку создаёт машинный ключ до появления любого проекта, а все
        остальные списки — по проекту. Возвращает ТОЛЬКО машинные ключи
        (`especie: "maquina"`, `projectId: null`) и ТОЛЬКО ключи вызывающего —
        ключи проекта остаются в списке своего проекта, и никто не видит ключи
        другого пользователя (RN-519). Отозванные ключи ВКЛЮЧЕНЫ: регистрация
        нового машинного ключа отзывает предыдущий (RN-552), и именно в этом
        списке это видно. `lastUsedAt` — записанное использование, никогда не
        живое соединение; null означает, что ключ ни разу не использовался.
      operationId: MachineDeviceKeysController_listMachineDeviceKeys
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/RunnerDeviceKeyListResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: >-
        Перечисляет собственные МАШИННЫЕ ключи устройств аутентифицированного
        пользователя
      tags:
        - users
  /users/me/machine-device-keys/{deviceKeyId}:
    delete:
      description: >-
        Идемпотентно — повторный отзыв не является ошибкой. Тот же отзыв, что и
        `DELETE /projects/{projectId}/runner-device-keys/{deviceKeyId}`: он
        также разрывает ЖИВЫЕ соединения, открытые с этим ключом, во ВСЕХ
        проектах (RN-520/RN-543). Начиная с RN-685 (ADR 0201) целью является
        КЛЮЧ: другой runner того же пользователя, подключённый с PAT или другим
        ключом, остаётся. Ещё не использованные тикеты ключа аннулируются. Если
        проектов ещё нет, разрывать нечего. Ключ ПРОЕКТА, несуществующий ключ и
        ключ другого пользователя получают один и тот же 404.
      operationId: MachineDeviceKeysController_revokeMachineDeviceKey
      parameters:
        - name: deviceKeyId
          required: true
          in: path
          schema:
            type: string
      responses:
        '204':
          description: Ключ отозван. Без тела.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '404':
          description: >-
            Ни один машинный ключ с этим id не принадлежит вызывающему (он не
            существует, является ключом проекта или чужим — один ответ для
            всех).
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: >-
        Отзывает один из собственных МАШИННЫХ ключей устройств
        аутентифицированного пользователя
      tags:
        - users
  /users/me/preferences:
    get:
      operationId: UserPreferencesController_get
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserPreferencesResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: >-
        Читает язык интерфейса и язык ответов агентов аутентифицированного
        пользователя
      tags:
        - users
    patch:
      description: >-
        Оба поля необязательны и независимы — отправка одного никогда не
        затрагивает другое, а тело без обоих — это 400. `locale` (интерфейс)
        остаётся ограниченным списком `pt-BR`/`en`. `responseLanguage` (RN-618)
        — это `automatico` или любой распознанный код BCP-47, хранимый в
        каноническом виде; нераспознанный код — это 400, и ничего из тела не
        записывается.
      operationId: UserPreferencesController_update
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateUserPreferencesDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserPreferencesResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: >-
        Записывает язык интерфейса и/или язык ответов агентов
        аутентифицированного пользователя
      tags:
        - users
  /users/me/preferences/detected-language:
    post:
      description: >-
        Единственный способ, которым обнаруженный язык становится предпочтением
        (RN-624): одно лишь обнаружение никогда ничего не меняет. `confirm`
        записывает `detectedLanguage` + `detectedLanguageConfirmedAt`; `decline`
        записывает отказ, чтобы о том же языке больше не спрашивали. Возвращает
        предпочтения после ответа.
      operationId: UserPreferencesController_answerDetected
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AnswerDetectedLanguageDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserPreferencesResponseDto'
        '400':
          description: Код, не являющийся распознанным языком BCP-47.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '409':
          description: >-
            `deteccao_mudou`: при `confirm` ваши недавние сообщения больше не
            указывают на этот язык — ничего не записано.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Отвечает на вопрос об обнаруженном языке
      tags:
        - users
  /workspaces:
    get:
      description: >-
        Уже отфильтровано по связи — глобального списка нет. Каждый элемент
        несёт собственную роль вызывающего.
      operationId: WorkspacesController_list
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/WorkspaceComPapelResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в рабочем пространстве.
        '404':
          description: Рабочее пространство не существует или невидимо для вызывающего.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Перечисляет рабочие пространства вызывающего
      tags:
        - workspaces
    post:
      description: >-
        Роль не требуется: создатель становится `owner`. Это единственная точка
        входа для того, кто ещё не принадлежит ни одному рабочему пространству.
      operationId: WorkspacesController_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWorkspaceDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkspaceResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в рабочем пространстве.
        '404':
          description: Рабочее пространство не существует или невидимо для вызывающего.
        '409':
          description: Рабочее пространство с таким slug уже существует.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Создаёт рабочее пространство
      tags:
        - workspaces
  /workspaces/{workspaceId}:
    get:
      operationId: WorkspacesController_get
      parameters:
        - name: workspaceId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkspaceResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в рабочем пространстве.
        '404':
          description: Рабочее пространство не существует или невидимо для вызывающего.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Возвращает рабочее пространство по id
      tags:
        - workspaces
    patch:
      operationId: WorkspacesController_update
      parameters:
        - name: workspaceId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateWorkspaceDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkspaceResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в рабочем пространстве.
        '404':
          description: Рабочее пространство не существует или невидимо для вызывающего.
        '409':
          description: Рабочее пространство с таким slug уже существует.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Меняет имя или slug рабочего пространства
      tags:
        - workspaces
    delete:
      description: Требует `owner`. Забирает с собой свои проекты, сессии и их историю.
      operationId: WorkspacesController_remove
      parameters:
        - name: workspaceId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkspaceResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в рабочем пространстве.
        '404':
          description: Рабочее пространство не существует или невидимо для вызывающего.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Удаляет рабочее пространство
      tags:
        - workspaces
  /workspaces/{workspaceId}/containers:
    get:
      description: >-
        Одна строка на проект рабочего пространства — начиная с RN-521 проект,
        ни разу не создававший контейнер, ПРИСУТСТВУЕТ с `registrado: null`, а
        не отсутствует, потому что эта страница — путь человека для запуска
        первого. Наблюдаемое состояние запрашивается у broker только для строк
        `provisioning`/`running` и только в пределах бюджета на загрузку — см.
        `naoVerificado` у пропущенных строк и ADR 0136 с обоснованием.
      operationId: ContainersOverviewController_list
      parameters:
        - name: workspaceId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ContainerOverviewItemResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в рабочем пространстве.
        '404':
          description: Рабочее пространство не существует или невидимо для вызывающего.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: >-
        Перечисляет каждый проект рабочего пространства и его контейнер, если он
        есть
      tags:
        - projects
  /workspaces/{workspaceId}/credential-spend:
    get:
      description: >-
        Существует, потому что ключ, который тратят агенты, принадлежит
        ВЛАДЕЛЬЦУ рабочего пространства (RN-058), — тот, кто оплачивает счёт,
        должен видеть счёт. Группирует по провайдеру, потому что это единица
        учётных данных, и отделяет то, что ушло через АГЕНТА, от того, что ушло
        через человека в чате: оба выходят из одного ключа и отвечают на разные
        вопросы. Не возвращает никаких секретов.
      operationId: BudgetsController_getCredentialSpend
      parameters:
        - name: workspaceId
          required: true
          in: path
          schema:
            type: string
        - name: meses
          required: true
          in: query
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CredentialSpendResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Требует `owner` в рабочем пространстве.
        '404':
          description: Проект или сессия не найдены.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Сколько потратили ключи владельца, по провайдерам и по месяцам
      tags:
        - llm
  /workspaces/{workspaceId}/huggingface/models:
    get:
      description: >-
        По умолчанию фильтрует только ОФИЦИАЛЬНЫХ издателей (список разрешённых
        в `domain/llm/huggingface-official-publishers.ts`) — тот же дух «всегда
        ручная курация», что и у каталога LLM (ADR 0042), применённый к значку
        «официальный», а не к активации. `includeCommunity=true` включает всех
        издателей, каждый помечен `official: false`, чтобы экран показал
        предупреждение о безопасности, — никогда не скрывается.
      operationId: HuggingFaceModelsController_search
      parameters:
        - name: q
          required: true
          in: query
          description: Текст поиска.
          schema:
            type: string
        - name: includeCommunity
          required: false
          in: query
          description: Передайте `true`, чтобы также включить неофициальных издателей.
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/HuggingFaceModelResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Требует `maintainer` в рабочем пространстве.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Ищет модели GGUF в Hugging Face Hub
      tags:
        - llm
  /workspaces/{workspaceId}/huggingface/pull-requests:
    post:
      description: >-
        Первый из двух явных шагов: этот вызов лишь записывает намерение, в
        `pending_confirmation`. Ничего не скачивается до отдельного вызова `POST
        .../pull-requests/:id/confirm` — продукт никогда не выполняет
        автоматическое молчаливое скачивание.
      operationId: HuggingFaceModelsController_createPullRequest
      parameters:
        - name: workspaceId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RequestModelPullDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelPullRequestResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Требует `maintainer` в рабочем пространстве.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Создаёт ОЖИДАЮЩИЙ запрос на скачивание — пока ничего не скачивается
      tags:
        - llm
  /workspaces/{workspaceId}/huggingface/pull-requests/{id}:
    get:
      operationId: HuggingFaceModelsController_getStatus
      parameters:
        - name: workspaceId
          required: true
          in: path
          schema:
            type: string
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelPullRequestResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Требует `maintainer` в рабочем пространстве.
        '404':
          description: Запрос на скачивание не найден.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Статус запроса на скачивание, для опроса фронтендом
      tags:
        - llm
  /workspaces/{workspaceId}/huggingface/pull-requests/{id}/confirm:
    post:
      description: >-
        Переводит `pending_confirmation` → `confirmed` → `pulling`, вызывает
        демон Ollama и при успехе делает upsert модели в каталог (`models`) и
        активирует её для ЭТОГО рабочего пространства. При сбое запрос
        заканчивается в `failed` с причиной, указывающей источник (infra | model
        | code | policy), — никогда не молчаливый сбой. Этот вызов выполняется
        СИНХРОННО в течение всего скачивания (почему — см. сценарий
        использования); используйте маршрут GET ниже для опроса, а не держите
        это соединение открытым.
      operationId: HuggingFaceModelsController_confirm
      parameters:
        - name: workspaceId
          required: true
          in: path
          schema:
            type: string
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelPullRequestResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Требует `maintainer` в рабочем пространстве.
        '404':
          description: Запрос на скачивание не найден.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Второе подтверждение — действительно выполняет скачивание
      tags:
        - llm
  /workspaces/{workspaceId}/llm-providers/credentialed:
    get:
      description: >-
        The owner of record (`workspaces.created_by`) is whose key the agents
        spend (RN-058). Only provider NAMES — never a secret, id or date.
        `ollama` (local, no key) never appears.
      operationId: ModelsController_credentialed
      parameters:
        - name: workspaceId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CredentialedProvidersResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Lists the LLM providers the workspace owner has a key for
      tags:
        - llm
  /workspaces/{workspaceId}/members:
    get:
      description: >-
        Кто связан с РАБОЧИМ ПРОСТРАНСТВОМ, с ролью рабочего пространства —
        ролью, которую наследует каждый проект, если строка проекта её не
        переопределяет. Дополняет `GET projects/:projectId/members`, который
        перечисляет только строки проекта: тот, кто попадает в проект только
        через рабочее пространство, появляется здесь, а не там. Только id, имя,
        email и роль.
      operationId: WorkspacesController_listMembers
      parameters:
        - name: workspaceId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/WorkspaceMemberComUsuarioResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в рабочем пространстве.
        '404':
          description: Рабочее пространство не существует или невидимо для вызывающего.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Перечисляет участников рабочего пространства
      tags:
        - workspaces
    post:
      description: >-
        Только `owner` может менять состав участников. Роль здесь наследуют ВСЕ
        проекты рабочего пространства. Эта роль НЕОБХОДИМА, но не ДОСТАТОЧНА:
        изменение СВОЕЙ СОБСТВЕННОЙ роли здесь отклоняется с 403 в обе стороны и
        не может быть нигде включено. Уровня выше, который удержал бы падение,
        нет, поэтому самопонижение было бы невосстановимо через интерфейс.
        Понижение ДРУГОГО `owner` по-прежнему разрешено — это единственный
        способ отозвать владение.
      operationId: WorkspacesController_addMember
      parameters:
        - name: workspaceId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AddMemberDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkspaceMemberResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: >-
            Не `owner` рабочего пространства ИЛИ цель — сам вызывающий, и
            запрошенная роль отличается от текущей (потолок самоизменения).
        '404':
          description: Рабочее пространство не существует или невидимо для вызывающего.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Связывает пользователя с рабочим пространством
      tags:
        - workspaces
  /workspaces/{workspaceId}/members/{userId}:
    delete:
      description: >-
        Только `owner`, как и upsert. Удаляет строку рабочего пространства И, в
        той же транзакции, строки пользователя во всех проектах этого рабочего
        пространства (роль проекта переопределяет роль рабочего пространства,
        поэтому их сохранение оставило бы пользователя внутри этих проектов), а
        также ключи устройств ПРОЕКТА и personal access token пользователя в
        них. После этого живое соединение runner этого пользователя в каждом
        проекте разрывается, по мере возможности. Машинные ключи устройств и
        открытые сессии не затрагиваются — они перестают достигать этого
        рабочего пространства, потому что роль разрешается в отсутствие.


        Удаление СЕБЯ отклоняется с 403 и не может быть нигде включено: уровня
        выше, который удержал бы падение, нет. Это же условие защищает
        последнего `owner` — только `owner` может вызвать этот маршрут, и никто
        не удаляет себя, поэтому каждый успешный вызов оставляет как минимум
        вызывающего в роли `owner`. Удаление ДРУГОГО `owner` разрешено — так
        отзывается владение, — кроме ТИТУЛЬНОГО ВЛАДЕЛЬЦА (`createdBy`),
        отклоняемого с 409 `criador_do_workspace`, пока `PUT
        :workspaceId/owner-of-record` не передаст титул другому владельцу: ключ
        LLM, который тратят агенты, и отчёт о расходах принадлежат ему.
        Идемпотентно: у цели без строки рабочего пространства всё равно
        очищаются строки проектов этого рабочего пространства, и она получает
        `204`.
      operationId: WorkspacesController_removeMember
      parameters:
        - name: workspaceId
          required: true
          in: path
          schema:
            type: string
        - name: userId
          required: true
          in: path
          schema:
            type: string
      responses:
        '204':
          description: Связь удалена. Без тела.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: >-
            Не `owner` рабочего пространства ИЛИ цель — сам вызывающий (потолок
            самоудаления).
        '404':
          description: Рабочее пространство не существует или невидимо для вызывающего.
        '409':
          description: >-
            `criador_do_workspace`: цель — титульный владелец; сначала передайте
            титул.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Отвязывает пользователя от рабочего пространства
      tags:
        - workspaces
  /workspaces/{workspaceId}/model-binding:
    get:
      description: СЫРОЙ binding этой области действия. Это корень каскада.
      operationId: ModelBindingsController_getWorkspaceBinding
      parameters:
        - name: workspaceId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelBindingResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в области действия.
        '404':
          description: Область действия не найдена.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Читает binding модели рабочего пространства
      tags:
        - llm
    put:
      description: Применяется к каждому проекту, у которого нет собственного binding.
      operationId: ModelBindingsController_setWorkspaceBinding
      parameters:
        - name: workspaceId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SetModelBindingDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelBindingResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в области действия.
        '404':
          description: Область действия не найдена.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Задаёт модель рабочего пространства по умолчанию
      tags:
        - llm
  /workspaces/{workspaceId}/models/{modelId}/billing:
    patch:
      description: >-
        Same curation level as the pricing route. `subscription` makes the spend
        report show the cost apart from the US$ paid (RN-954). Never re-prices
        the past.
      operationId: ModelsController_updateBilling
      parameters:
        - name: modelId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateModelBillingDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Требует `owner` в рабочем пространстве.
        '404':
          description: Модель не найдена.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Sets a model's curated billing mark (api | subscription | free)
      tags:
        - llm
  /workspaces/{workspaceId}/models/{modelId}/price-changes:
    get:
      description: >-
        Сначала самые свежие. Это неизменяемый журнал: никогда не получает
        UPDATE, а строка несёт пару до/после, чтобы аудит не зависел от
        восстановления «до» по предыдущей строке.
      operationId: ModelsController_priceChanges
      parameters:
        - name: modelId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ModelPriceChangeResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в рабочем пространстве.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: История изменений цены модели
      tags:
        - llm
  /workspaces/{workspaceId}/models/{modelId}/pricing:
    patch:
      description: >-
        Применяется с этого момента и НИКОГДА не переоценивает прошлое: каждая
        строка `token_usage` хранит цену, давшую её стоимость. Изменение
        остаётся записанным с парой до/после — его можно запросить на маршруте
        истории.
      operationId: ModelsController_updatePricing
      parameters:
        - name: modelId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateModelPricingDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Требует `owner` в рабочем пространстве.
        '404':
          description: Модель не найдена.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Исправляет цену модели
      tags:
        - llm
  /workspaces/{workspaceId}/models/activate:
    post:
      description: >-
        Курация владельца, ПРИМЕНЯЕМАЯ ТОЛЬКО К ЭТОМУ РАБОЧЕМУ ПРОСТРАНСТВУ (ADR
        0049). Затрагивает только `isActive` — `availability` — это то, что
        синхронизация наблюдала у провайдера, оно глобально и здесь не меняется.
        Весь пакет или ничего: несуществующий id приводит к сбою вызова без
        применения какой-либо строки.
      operationId: ModelsController_activate
      parameters:
        - name: workspaceId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SetModelsActiveDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ModelComCuradoriaResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Требует `owner` в рабочем пространстве.
        '404':
          description: Какой-то id в пакете не существует.
        '422':
          description: >-
            Активация псевдонима свободной маршрутизации OpenRouter (`~…`) —
            `code: "alias_de_roteamento_livre"`, с отклонёнными `modelIds`.
            Отклоняется весь пакет. Деактивация всегда разрешена (AT-271,
            RN-679).
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Включает или выключает модели в селекторе (пакетно)
      tags:
        - llm
  /workspaces/{workspaceId}/models/catalog:
    get:
      description: >-
        Экран курации. Модель, обнаруженная синхронизацией, приходит как
        `isActive: false` и появляется только здесь, пока кто-нибудь её не
        активирует; модель, исчезнувшая у провайдера, приходит с `availability:
        "unavailable"` и никогда не удаляется, потому что на неё указывают
        binding и история затрат. `isActive` — ДЛЯ ЭТОГО рабочего пространства
        (ADR 0049); `availability` глобально, потому что это то, что
        синхронизация наблюдала у провайдера.
      operationId: ModelsController_catalog
      parameters:
        - name: workspaceId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                properties:
                  local:
                    type: object
                    additionalProperties:
                      type: array
                      items:
                        $ref: '#/components/schemas/ModeloDoCatalogoResponseDto'
                  cloud:
                    type: object
                    additionalProperties:
                      type: array
                      items:
                        $ref: '#/components/schemas/ModeloDoCatalogoResponseDto'
                example:
                  local:
                    ollama: []
                  cloud:
                    anthropic: []
                    openai: []
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в рабочем пространстве.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Перечисляет ВЕСЬ каталог, включая неактивные и недоступные
      tags:
        - llm
  /workspaces/{workspaceId}/models/sync:
    post:
      description: >-
        Тот же сценарий использования, что и у внутреннего маршрута, который
        планирует engine, — у кнопки обновления на экране курации нет
        собственного пути, поэтому никогда не бывает двух сверок, которые могли
        бы разойтись.
      operationId: ModelsController_sync
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SyncModelCatalogResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Требует `owner` в рабочем пространстве.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: >-
        Принудительно выполняет синхронизацию каталога, которую делает
        периодическое задание, прямо сейчас
      tags:
        - llm
  /workspaces/{workspaceId}/models/uses:
    post:
      description: >-
        Ось, которую не публикует ни один каталог: «хорош для кода» — не
        возможность, объявленная каким-либо провайдером, это мнение оператора —
        и применяется ТОЛЬКО к этому рабочему пространству (ADR 0049). Ось
        независима от активации: пометка применения не включает модель в
        селекторе, а изменение применения не выключает то, что было включено.
        Список применений ЗАМЕНЯЕТ предыдущий.
      operationId: ModelsController_uses
      parameters:
        - name: workspaceId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SetModelUsesDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ModelComCuradoriaResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Требует `owner` в рабочем пространстве.
        '404':
          description: Какой-то id в пакете не существует.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: >-
        Отмечает, для чего рабочее пространство использует каждую модель
        (пакетно)
      tags:
        - llm
  /workspaces/{workspaceId}/owner-of-record:
    put:
      description: >-
        Титульный владелец — это `createdBy`: чьи учётные данные LLM тратят
        агенты и чьи учётные данные git используют действия агентов (RN-058), и
        кому принадлежит отчёт о расходах (RN-060). Он не даёт авторизации — её
        дают роли. Цель уже должна быть `owner` рабочего пространства (иначе 409
        `titular_precisa_ser_owner`). С момента коммита ходы агентов ищут
        учётные данные НОВОГО титульного владельца; если у него нет учётных
        данных для провайдера модели, ход заканчивается существующим исходом
        «учётные данные не зарегистрированы» для этого провайдера — этот маршрут
        не проверяет это заранее. Вызвать его может любой `owner`, в том числе
        для другого владельца. Передача текущему титульному владельцу ничего не
        делает.
      operationId: WorkspacesController_transferOwnership
      parameters:
        - name: workspaceId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TransferOwnershipDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkspaceResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в рабочем пространстве.
        '404':
          description: Рабочее пространство не существует или невидимо для вызывающего.
        '409':
          description: >-
            `titular_precisa_ser_owner`: цель не является `owner` рабочего
            пространства.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Передаёт титульное владение рабочим пространством
      tags:
        - workspaces
  /workspaces/{workspaceId}/project-folders:
    get:
      description: >-
        Выбор папки для режима Mounted, обслуживаемый api, а не runner (RN-504).
        ЖЁСТКО ограничен `BRABO_PROJECTS_BASE` (ADR 0141): `path` необязателен и
        по умолчанию равен самой базе, а любой `path` вне её — это `400`,
        некорректный запрос, а не проблема прав, поскольку ни одна роль не
        просматривает что-либо вне базы.


        Ограничен намеренно: только каталоги, не более 500 (отсортированных ДО
        обрезки, о чём говорит `truncado`), никогда рекурсивно, записи с точкой
        в начале исключены, а символические ссылки сообщаются, но никогда не
        разыменовываются. То, что осталось за бортом, ПОДСЧИТЫВАЕТСЯ
        (`arquivos`, `simbolicos`), чтобы папка, полная кода, никогда не
        выглядела пустой.


        Парного POST нет: создание папки относится к материализации
        смонтированной рабочей области, а не к выбору.
      operationId: WorkspacesController_listProjectFolders
      parameters:
        - name: path
          required: false
          in: query
          description: >-
            Абсолютный путь для перечисления. Если опущен — сама база. Должен
            быть внутри `BRABO_PROJECTS_BASE`; `..`, `.` и относительные пути
            сразу отклоняются, а не разрешаются.
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectFoldersResponseDto'
        '400':
          description: Путь вне базы проектов или некорректен.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в рабочем пространстве.
        '404':
          description: Рабочее пространство не существует или невидимо для вызывающего.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Перечисляет подпапки папки внутри базы проектов
      tags:
        - workspaces
  /workspaces/{workspaceId}/projects:
    get:
      operationId: WorkspacesController_listProjects
      parameters:
        - name: workspaceId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ProjectResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в рабочем пространстве.
        '404':
          description: Рабочее пространство не существует или невидимо для вызывающего.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Перечисляет проекты рабочего пространства
      tags:
        - workspaces
    post:
      operationId: WorkspacesController_createProjectInWorkspace
      parameters:
        - name: workspaceId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateProjectDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в рабочем пространстве.
        '404':
          description: Рабочее пространство не существует или невидимо для вызывающего.
        '409':
          description: Проект с таким slug уже существует в рабочем пространстве.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Создаёт проект в рабочем пространстве
      tags:
        - workspaces
  /workspaces/{workspaceId}/projects-base:
    get:
      description: >-
        Единственная папка на машине оператора, которую видят контейнеры api и
        engine, смонтированная по идентичности (ADR 0141). `null` — нормальное
        состояние, никогда не ошибка, — означает, что у этой установки нет
        `BRABO_PROJECTS_BASE`, поэтому мастер проекта вообще не должен
        предлагать режим Mounted. Одно и то же значение для каждого рабочего
        пространства: это конфигурация установки, а `workspaceId` лишь
        ограничивает авторизацию.


        `brokerConfigurado` отвечает на вторую половину того же вопроса:
        `container` и `mounted` поднимают контейнер только через broker (ADR
        0144), поэтому без него мастер не выбирает заранее Mounted и предлагает
        вместо этого Runner (ADR 0161, RN-573).
      operationId: WorkspacesController_getProjectsBase
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectsBaseResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в рабочем пространстве.
        '404':
          description: Рабочее пространство не существует или невидимо для вызывающего.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Базовая папка для проектов в режиме Mounted
      tags:
        - workspaces
  /workspaces/{workspaceId}/projects-status:
    get:
      description: >-
        Одна строка на проект рабочего пространства с заблокированной задачей —
        питает точку статуса в боковой панели дашборда одним запросом вместо N
        вызовов к backlog.
      operationId: WorkspacesController_getProjectsStatus
      parameters:
        - name: workspaceId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ProjectBlockedStatusResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в рабочем пространстве.
        '404':
          description: Рабочее пространство не существует или невидимо для вызывающего.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Количество заблокированных задач по проектам
      tags:
        - workspaces
  /workspaces/{workspaceId}/projects-summary:
    get:
      description: >-
        Одна строка на проект рабочего пространства с git-провайдером, статусом
        создания, бюджетом, последней активностью, историями, ожидающими
        продвижения, и фактами, определяющими состав агентов (RN-090).


        Та же идея, что и `projects-status`, этажом выше: сетка карточек — это
        чтение РАБОЧЕГО ПРОСТРАНСТВА, а не N чтений проектов. Прежний путь делал
        семь опрашиваемых запросов на карточку, и при 23 проектах один только
        дашборд пробивал лимит в 300 запросов/мин.
      operationId: WorkspacesController_getProjectsSummary
      parameters:
        - name: workspaceId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ProjectCardSummaryResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в рабочем пространстве.
        '404':
          description: Рабочее пространство не существует или невидимо для вызывающего.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Всё, что отображают карточки дашборда, за один вызов
      tags:
        - workspaces
  /workspaces/{workspaceId}/spend-report:
    get:
      description: >-
        Аудитория ВЛАДЕЛЬЦА. Ось ПРОВАЙДЕРА вернулась (ADR 0076, RN-186) и
        находится здесь, а не на маршруте участника, потому что разбивка по
        провайдеру — это разбивка по УЧЁТНЫМ ДАННЫМ, а учётные данные — дело
        того, кто платит (RN-060). Дополняет `credential-spend`, который
        продолжает отвечать на вопрос о СЧЁТЕ (по месяцам, с привязкой к
        существующему сегодня ключу). Человек и агент также идут отдельными
        блоками, по `actor_kind`. Скользящее окно в днях, а дневной ряд приходит
        ПЛОТНЫМ — день без расходов приходит как ноль, иначе спарклайн лжёт о
        темпе.
      operationId: SpendController_getWorkspaceSpendReport
      parameters:
        - name: workspaceId
          required: true
          in: path
          schema:
            type: string
        - name: dias
          required: false
          in: query
          description: Скользящее окно. По умолчанию 30, максимум 180.
          schema:
            type: string
        - name: projectId
          required: false
          in: query
          description: >-
            Slice to ONE project of this workspace (RN-952). A project from
            another workspace answers 404.
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkspaceSpendReportResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Требует `owner` в рабочем пространстве.
        '404':
          description: Project not in this workspace.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: >-
        Разбивает расходы рабочего пространства по модели, провайдеру, проекту,
        актору и дню
      tags:
        - llm
  /workspaces/{workspaceId}/spend/me:
    get:
      description: >-
        The non-owner's global page (RN-953). Only the caller's rows, by PROJECT
        and by day, without provider or credential.
      operationId: SpendController_getMyWorkspaceSpend
      parameters:
        - name: workspaceId
          required: true
          in: path
          schema:
            type: string
        - name: dias
          required: false
          in: query
          description: Скользящее окно. По умолчанию 30, максимум 180.
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MyWorkspaceSpendResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: My own consumption in this workspace, summed by project
      tags:
        - llm
  /workspaces/{workspaceId}/summary:
    get:
      description: >-
        Число проектов, агенты, потратившие токены в этом месяце, и расходы
        этого месяца — питает строку сводки дашборда проектов.
      operationId: WorkspacesController_getSummary
      parameters:
        - name: workspaceId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkspaceSummaryResponseDto'
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в рабочем пространстве.
        '404':
          description: Рабочее пространство не существует или невидимо для вызывающего.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Агрегированная сводка рабочего пространства
      tags:
        - workspaces
  /workspaces/{workspaceId}/tool-router:
    put:
      description: >-
        Включено по умолчанию. Когда включено И модель хода от OpenRouter, шаг
        агента с двумя или более инструментами сначала спрашивает Jev, какой
        инструмент подходит, и модели чата предлагается только этот инструмент и
        предыдущий. Jev никогда ничего не одобряет и не отклоняет: действие,
        требующее одобрения, по-прежнему его требует. Любой сбой Jev
        откатывается ко всему каталогу. Выключение прекращает все вызовы к Jev,
        а вместе с ними и его затраты.
      operationId: WorkspacesController_setToolRouter
      parameters:
        - name: workspaceId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SetToolRouterDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkspaceResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в рабочем пространстве.
        '404':
          description: Рабочее пространство не существует или невидимо для вызывающего.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: >-
        Включает или выключает маршрутизацию инструментов Jev для рабочего
        пространства
      tags:
        - workspaces
  /workspaces/{workspaceId}/unread-events:
    post:
      description: >-
        Выдвижная панель колокольчика для всего рабочего пространства (RN-091).
        Каждый курсор говорит, до какого `seq` этот проект уже прочитан, а ответ
        несёт то, что пришло после этого в самой свежей сессии каждого.


        **Это POST, потому что это единственный глагол с ТЕЛОМ, а не потому, что
        он меняет состояние.** Сервер не знает, где остановился каждый читатель:
        «прочитано до сюда» — это `seq` на проект, хранимый в браузере, и
        endpoint «отметить как прочитанное» (намеренно) нет. Поэтому граница
        должна ПЕРЕДАВАТЬСЯ в запросе, а пар десятки — в query string это
        превращается в длинный URL, который прокси обрезает, и к тому же
        поместило бы id проектов пользователя в журналы доступа. Маршрут
        идемпотентен и без побочных эффектов: отвечает `200`, никогда `201`.


        Заменяет по одному запросу НА ПРОЕКТ при открытой панели — 23 проекта
        стоили 286 запросов/мин при лимите 300.
      operationId: WorkspacesController_getUnreadEvents
      parameters:
        - name: workspaceId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UnreadEventsDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ProjectUnreadEventsResponseDto'
        '400':
          description: >-
            Некорректное тело. `ValidationPipe` работает с `whitelist` и
            `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к
            ошибке.
        '401':
          description: Нет токена, токен истёк или подпись недействительна.
        '403':
          description: Недостаточная роль в рабочем пространстве.
        '404':
          description: Рабочее пространство не существует или невидимо для вызывающего.
        '429':
          description: Ограничение частоты запросов на пользователя или на IP.
      security:
        - bearer: []
      summary: Непрочитанные события по нескольким проектам, за один вызов
      tags:
        - workspaces
info:
  title: Brabo API
  description: >-
    API Brabo. Каждый endpoint возвращает JSON, кроме четырёх явно

    задокументированных с другим `content-type` (два SSE, метрики

    и OAuth-callback, который перенаправляет).


    ## Аутентификация


    Access-токен — это **EdDSA JWT на 15 минут**, предъявляемый в

    `Authorization: Bearer <token>`. Публичный ключ находится по адресу

    `GET /.well-known/jwks.json`.


    Refresh **не является** JWT: это непрозрачный токен, который живёт в

    cookie `httpOnly` (`brabo_refresh`, `Path=/auth`, `SameSite=Strict`) и

    никогда не появляется ни в одном теле ответа. `POST /auth/refresh` и

    `POST /auth/logout` читают cookie и требуют, чтобы заголовок `X-CSRF-Token`

    совпадал с cookie `brabo_csrf`.


    **Ротация обязательна.** Каждый refresh расходует предъявленный токен и

    выдаёт другой. Повторное предъявление уже израсходованного — признак кражи,

    и оно отзывает ВСЁ семейство, включая законную сессию. Клиент, который

    отправляет два одновременных refresh, выводит из строя сам себя; используйте

    один вызов в полёте.


    Маршруты `/internal/*` — это поверхность api ↔ engine, и они находятся **вне

    JWT**: они требуют заголовок `X-Brabo-Service-Token` с общим

    секретом. Токен пользователя не открывает ни один из них.


    ## Ошибки


    Формат NestJS по умолчанию: `{ "statusCode": number, "message": string |
    string[], "error": string }`.


    | код | когда |

    |---|---|

    | `400` | некорректное тело — `ValidationPipe` работает с `whitelist` и
    `forbidNonWhitelisted`, поэтому неизвестное поле тоже приводит к ошибке |

    | `401` | нет токена, токен истёк или недействительные учётные данные |

    | `403` | аутентифицирован, но без роли, которую требует RBAC, или неверный
    сервисный токен |

    | `404` | ресурс не существует **или** невидим для вызывающего |

    | `409` | конфликт состояния — недопустимый переход сессии, ресурс уже
    существует |

    | `429` | лимит частоты или прогрессивная блокировка |


    Маршруты аутентификации отвечают **намеренно неразличимо** для
    несуществующего

    email, неверного пароля и заблокированной учётной записи: то же тело, тот же
    статус, то же

    время. Именно это закрывает перебор учётных записей, и это не дефект

    диагностики.


    ## Лимит частоты


    Скользящее окно в Postgres: 300 запросов на пользователя и 600 на IP каждые
    60 с,

    настраивается. Маршрут `@Public()` и сервисный маршрут **освобождены** —
    `/auth/*`

    защищает прогрессивная блокировка, а не лимит частоты.
  version: 0.1.0
  contact: {}
tags:
  - name: auth
    x-displayName: аутентификация
  - name: gates
    x-displayName: gate
  - name: git
    x-displayName: git
  - name: infrastructure
    x-displayName: инфраструктура
  - name: internal
    x-displayName: внутреннее
  - name: llm
    x-displayName: llm
  - name: projects
    x-displayName: проекты
  - name: actions
    x-displayName: действия
  - name: execution
    x-displayName: выполнение
  - name: anamnesis
    x-displayName: анамнез
  - name: backlog
    x-displayName: backlog
  - name: psychologist
    x-displayName: психолог
  - name: rag
    x-displayName: rag
  - name: sessions
    x-displayName: сессии
  - name: agents
    x-displayName: агенты
  - name: credentials
    x-displayName: учётные данные
  - name: users
    x-displayName: пользователи
  - name: workspaces
    x-displayName: рабочие пространства
servers: []
components:
  securitySchemes:
    bearer:
      scheme: bearer
      bearerFormat: JWT
      type: http
    service-token:
      type: apiKey
      in: header
      name: X-Brabo-Service-Token
  schemas:
    AcceptParallelizationDto:
      type: object
      properties:
        module:
          type: string
          example: api
          description: >-
            Имя модуля, как оно указано в `module_map`. Запускает выделенного
            ему dev agent в изолированном worktree.
      required:
        - module
    AceiteAutomaticoResponseDto:
      type: object
      properties:
        aceito:
          type: boolean
          example: true
        criterio:
          $ref: '#/components/schemas/CriterioDoAceiteResponseDto'
        motivo:
          type: string
          enum:
            - nao_e_po_para_arquiteto
            - oferta_nao_pendente
            - sem_regras_de_negocio
            - regras_sem_historia
            - repositorio_nao_local
            - credencial_de_git_no_projeto
            - autor_sem_papel
            - falhou
          example: regras_sem_historia
          description: >-
            Почему человеку всё ещё нужно кликнуть. `falhou`: система
            попыталась, и принятие не удалось — `handoff.auto_accept_failed`
            есть в журнале событий.
      required:
        - aceito
    AceiteResponseDto:
      type: object
      properties:
        message:
          type: string
          example: If the address is available, we've sent an email.
          description: >-
            Общее сообщение, одинаковое для известного и неизвестного адреса, —
            см. примечание о переборе в справочнике.
      required:
        - message
    ActivateExecutionDto:
      type: object
      properties:
        taskBudgetMicros:
          type: number
          example: 500000
          description: >-
            Бюджет на задачу в микро-USD. Если опущен, используется значение
            домена по умолчанию (DEFAULT_TASK_BUDGET_MICROS).
        maxGateCorrections:
          type: number
          example: 3
          description: >-
            Сколько циклов dev↔gate, прежде чем задача станет `blocked`. Именно
            это не даёт агенту тратить бюджет в цикле исправлений.
        devAgentImpl:
          type: string
          enum:
            - real
            - noop
          example: real
          description: >-
            `noop` запускает dev agent БЕЗ LLM, чтобы проверить инфраструктуру
            выполнения от начала до конца, не тратя токены. Если опущено,
            используется `real`.
        terminalAllowPatterns:
          example:
            - Terminal(pnpm test:*)
            - Terminal(pnpm lint)
          description: >-
            Шаблоны, разрешённые в `permissions.json`, чтобы dev agent запускали
            набор тестов. `deny` всё равно побеждает всё перечисленное здесь.
          type: array
          items:
            type: string
        originSessionId:
          type: string
          example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
          description: >-
            Id сессии ЧАТА, из которой пришёл клик «активировать выполнение», —
            творческой/консультационной сессии Dev Lead/PO, никогда не сессии
            выполнения. Если опущен (устаревший вызывающий, например активация
            из Обзора без контекста сессии), никакая сессия не закрывается, как
            и раньше. Если указан, она закрывается в конце — но только если у
            неё нет ожидающего handoff/действия/хода (RN-135), и никогда, если
            это сама сессия выполнения.
    ActorDto:
      type: object
      properties:
        kind:
          type: string
          enum:
            - user
            - agent
            - system
          example: agent
          description: Природа того, кто действовал.
        id:
          type: string
          example: dev-api
          description: Id пользователя, когда `kind=user`; slug агента, когда `kind=agent`.
      required:
        - kind
        - id
    ActorResponseDto:
      type: object
      properties:
        kind:
          type: string
          enum:
            - user
            - agent
            - system
          example: agent
          description: Природа того, кто действовал.
        id:
          type: string
          example: dev-api
          description: Id пользователя, когда `kind=user`; slug агента, когда `kind=agent`.
      required:
        - kind
        - id
    AddMemberDto:
      type: object
      properties:
        userId:
          type: string
          format: uuid
          example: 3f1b2c8e-5a4d-4b7e-9c10-2d6f8a1b4c33
          description: Id пользователя для связывания.
        role:
          type: string
          enum:
            - viewer
            - developer
            - maintainer
            - owner
          example: developer
          description: >-
            Роль в этой связи. В ПРОЕКТЕ она ПЕРЕОПРЕДЕЛЯЕТ роль рабочего
            пространства в обе стороны — эффективной является эта роль, пока
            связь существует, выше ИЛИ ниже, — и два действия отклоняются с 403:
            понижение `owner` рабочего пространства и изменение собственной роли
            (в любую сторону). В РАБОЧЕМ ПРОСТРАНСТВЕ это просто роль, только со
            вторым из этих потолков: свою собственную изменить нельзя.
      required:
        - userId
        - role
    AdoptRepositoryDto:
      type: object
      properties:
        externalId:
          type: string
          example: acme/checkout
          description: >-
            Идентификатор репозитория У ПРОВАЙДЕРА: `owner/repo` на github,
            `namespace/path` на gitlab, абсолютный путь к bare-репозиторию
            локально. Ничего не создаётся — репозиторий уже должен существовать,
            а зарегистрированные учётные данные должны давать к нему доступ.
      required:
        - externalId
    AdoptRepositoryResponseDto:
      type: object
      properties:
        repository:
          $ref: '#/components/schemas/ProvisionedRepositoryResponseDto'
        plan:
          description: >-
            DRY-RUN: в репозитории ничего не выполнено. Решите через `POST
            .../bootstrap/plan/approve` или `.../plan/skip`.
          allOf:
            - $ref: '#/components/schemas/BootstrapPlanResponseDto'
        alreadyAdopted:
          type: boolean
          example: false
          description: >-
            `true`, когда проект уже подключил ЭТОТ репозиторий, — ничего не
            создано, план просто пересоздан по текущему состоянию.
      required:
        - repository
        - plan
        - alreadyAdopted
    AdrRefResponseDto:
      type: object
      properties:
        actionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRC
          description: Id действия `open_adr_pr` (`proposed_action`), которое открыло ADR.
        title:
          type: string
          example: 0007 — Postgres queue instead of Redis
        status:
          type: string
          example: executed
        pullRequestUrl:
          type: object
          example: https://github.com/daneiel/brabo/pull/42
          nullable: true
          description: '`null`, пока PR фактически не открыт.'
      required:
        - actionId
        - title
        - status
        - pullRequestUrl
    AgentAutonomyRuleResponseDto:
      type: object
      properties:
        agentId:
          type: string
          example: dev-api
          description: Slug агента.
        actionType:
          type: string
          example: terminal
          description: >-
            Тип действия или `"*"` — «автоматический режим» (RN-153): автономия
            для ЛЮБОГО типа действия этого агента.
        mode:
          type: string
          enum:
            - auto_approve
            - require_approval
            - deny
          example: auto_approve
          description: >-
            Предоставленная автономия. Не переопределяет `permissions.json`:
            `deny` там по-прежнему побеждает.
      required:
        - agentId
        - actionType
        - mode
    AgenteAtivadoResponseDto:
      type: object
      properties:
        agent:
          type: string
          example: po
          description: Slug активированного агента.
        status:
          type: string
          example: active
          enum:
            - active
      required:
        - agent
        - status
    AgenteComVersoesResponseDto:
      type: object
      properties:
        agent:
          type: string
          example: dev-api
          description: >-
            Реальный slug, включая dev agent по модулям. Список НЕ исходит из
            статического состава — иначе реально существующие агенты не
            появились бы.
        versions:
          type: array
          items:
            $ref: '#/components/schemas/InstructionVersionResponseDto'
      required:
        - agent
        - versions
    AgentTokenUsageResponseDto:
      type: object
      properties:
        actorId:
          type: string
          example: dev-api
          description: Slug агента или id пользователя.
        costMicros:
          type: number
          example: 3400000
          description: Стоимость в микро-USD.
        inputTokens:
          type: number
          example: 128000
        outputTokens:
          type: number
          example: 14200
      required:
        - actorId
        - costMicros
        - inputTokens
        - outputTokens
    AlvoJaAtendidoResponseDto:
      type: object
      properties:
        toAgent:
          type: string
          enum:
            - infra
          example: infra
        motivo:
          type: string
          enum:
            - oferta_pendente
            - agente_ativo
      required:
        - toAgent
        - motivo
    AlvoQaInternalDto:
      type: object
      properties:
        qa:
          type: boolean
          example: true
        taskId:
          type: string
          format: uuid
      required:
        - qa
        - taskId
    AnaliseDeTerminoResponseDto:
      type: object
      properties:
        causa:
          type: string
          example: heartbeat_timeout
          description: >-
            ИСТОЧНИК сбоя, никогда не выведенный методом исключения (урок из ADR
            0020).
        estadoDaSessao:
          type: string
          example: closed_abnormally
        analise:
          type: string
          example: The agent stopped responding after a 90s tool call.
      required:
        - causa
        - estadoDaSessao
        - analise
    AnamneseContextResponseDto:
      type: object
      properties:
        competencyCatalog:
          example:
            - TypeScript
            - Elixir
            - Kubernetes
          description: >-
            ЗАКРЫТЫЙ каталог. Модель не может выпустить компетенцию вне него —
            проверка её отклоняет, — поэтому он передаётся в промпт списком.
          type: array
          items:
            type: string
        members:
          description: Уже ИСКЛЮЧАЕТ тех, кто отказался от профилирования.
          type: array
          items:
            $ref: '#/components/schemas/AnamneseMemberResponseDto'
        queuedHypotheses:
          description: >-
            ПРИНЯТЫЕ гипотезы Психолога, ожидающие превращения в приоритетный
            вход.
          type: array
          items:
            $ref: '#/components/schemas/AnamneseQueuedResponseDto'
        currentProfiles:
          type: array
          items:
            $ref: '#/components/schemas/AnamneseProfileResponseDto'
        instructions:
          type: array
          items:
            $ref: '#/components/schemas/AnamneseInstructionResponseDto'
        decisions:
          description: >-
            Одобрения и отклонения пользователя в окне. Они приходят отсюда,
            потому что их НЕТ в журнале событий.
          type: array
          items:
            $ref: '#/components/schemas/AnamneseDecisionResponseDto'
        windowFrom:
          type: object
          example: '2026-07-27T12:00:00.000Z'
          nullable: true
          description: >-
            Начало окна для анализа (конец последнего раунда). `null` в первом.
            Сами события engine читает прямо из Postgres — передавать их по HTTP
            было бы дороже, но не правильнее.
      required:
        - competencyCatalog
        - members
        - queuedHypotheses
        - currentProfiles
        - instructions
        - decisions
        - windowFrom
    AnamneseDecisionResponseDto:
      type: object
      properties:
        actionType:
          type: string
          example: terminal
        status:
          type: string
          example: denied
        rejectionReason:
          type: object
          example: Command would delete the directory.
          nullable: true
        decidedBy:
          type: object
          example: 01JC4Z0000USUARIO0000000001
          nullable: true
        decidedAt:
          type: object
          example: '2026-07-27T13:00:00.000Z'
          nullable: true
      required:
        - actionType
        - status
        - rejectionReason
        - decidedBy
        - decidedAt
    AnamneseInstructionResponseDto:
      type: object
      properties:
        agent:
          type: string
          example: dev-api
        version:
          type: number
          example: 4
        content:
          type: string
          example: |-
            # dev-api

            …
      required:
        - agent
        - version
        - content
    AnamneseMemberResponseDto:
      type: object
      properties:
        userId:
          type: string
          example: 01JC4Z0000USUARIO0000000002
        name:
          type: object
          example: Senior Dev
          nullable: true
        email:
          type: string
          example: dev@brabo.dev
        role:
          type: string
          example: developer
      required:
        - userId
        - name
        - email
        - role
    AnamneseProfileResponseDto:
      type: object
      properties:
        userId:
          type: string
          example: 01JC4Z0000USUARIO0000000002
        competency:
          type: string
          example: TypeScript
        level:
          type: string
          example: advanced
        rationale:
          type: string
          example: Fixed three generic typing errors without help.
      required:
        - userId
        - competency
        - level
        - rationale
    AnamneseQueuedResponseDto:
      type: object
      properties:
        queueId:
          type: string
          example: 01JC4Z0000FILA00000000000001
        hypothesisId:
          type: string
          example: 01JC4Z0000HIPOTESE000000001
        agenteAlvo:
          type: string
          example: dev-api
        hipotese:
          type: string
          example: The instructions don't say when the task is done.
        sugestao:
          type: string
          example: Add an explicit definition-of-done criterion.
        confiancaPercent:
          type: number
          example: 72
      required:
        - queueId
        - hypothesisId
        - agenteAlvo
        - hipotese
        - sugestao
        - confiancaPercent
    AnswerDetectedLanguageDto:
      type: object
      properties:
        language:
          type: string
          example: es
          maxLength: 35
          description: >-
            Язык, о котором был вопрос, — `detectionQuestion` из `GET
            .../sessions/:sessionId/response-language`.
        answer:
          type: string
          enum:
            - confirm
            - decline
          example: confirm
          description: >-
            `confirm` сохраняет его как ваш обнаруженный и подтверждённый язык
            (тогда он отвечает вам везде, где ваша Учётная запись на
            автоматическом режиме); 409 `deteccao_mudou`, когда ваши сообщения
            больше на него не указывают. `decline` сохраняет отказ, и об этом
            языке больше никогда не спрашивают (последующее подтверждение
            снимает отказ).
      required:
        - language
        - answer
    AnswerOpenQuestionDto:
      type: object
      properties:
        resposta:
          type: string
          example: 404 for a code that does not exist.
          description: 'Ответ свободным текстом. Отправьте это ИЛИ `aceitarSugestao: true`.'
        aceitarSugestao:
          type: boolean
          example: true
          description: Принять предложение агента как ответ.
    AnswerStructuredQuestionDto:
      type: object
      properties:
        answers:
          type: object
          additionalProperties:
            type: string
          example:
            name: Easy Checkout
            users: Small business owners
            platform: Web
          description: >-
            Ответ по `id` вопроса — тому же `id`, что пришёл в
            `chat.structured_question`.
      required:
        - answers
    AppDeExecucaoResponseDto:
      type: object
      properties:
        command:
          type: string
          example: npm start
        port:
          type: number
          example: 3000
      required:
        - command
        - port
    AppendSessionEventDto:
      type: object
      properties:
        type:
          type: string
          example: chat.message
          description: Тип события. Полный перечень — в `docs/reference/events.md`.
        actor:
          $ref: '#/components/schemas/ActorDto'
        payload:
          type: object
          additionalProperties: true
          example:
            role: user
            content: I want a new checkout.
          description: Свободная форма, своя для каждого `type`.
      required:
        - type
        - actor
        - payload
    AppendSessionEventInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        type:
          type: string
          example: agent.tool_call
          description: См. `docs/reference/events.md`.
        actorKind:
          type: string
          enum:
            - user
            - agent
            - system
          example: agent
        actorId:
          type: string
          example: dev-api
          description: Slug агента, который его выпустил.
        payload:
          type: object
          additionalProperties: true
          example:
            tool: ler_arquivo
      required:
        - projectId
        - type
        - actorKind
        - actorId
        - payload
    ApproveAlwaysResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRC
          description: ULID действия.
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        sessionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
        seq:
          type: number
          example: 7
          description: Порядок действия внутри сессии; используется как курсор в списках.
        actionType:
          type: string
          enum:
            - terminal
            - git_commit
            - git_push
            - pr_open
            - spend
            - git_repo_create
            - git_branch_create
            - git_branch_protect
            - write_file
            - open_adr_pr
            - git_merge
            - open_infra_pr
            - instruction_patch
            - parallelize
            - raise_max_parallel
            - propose_execution_plan
            - assess_implementability
            - container_start
            - container_stop
            - container_remove
            - container_recreate
            - container_start_via_runner
            - qa_environment_start
          example: terminal
          description: >-
            Что сделало бы действие. `git_push` и `git_merge` требуют
            `maintainer`.
        payload:
          type: object
          additionalProperties: true
          example:
            command: pnpm test
          description: Параметры действия, свои для каждого `actionType`.
        status:
          type: string
          enum:
            - pending
            - approved
            - denied
            - auto_approved
            - executed
            - failed
          example: pending
          description: >-
            Состояние в конвейере. `auto_approved` намеренно отличается от
            `approved`: журналу нужно различать, что решил человек и что
            разрешила политика.
        resolvedPolicy:
          type: string
          enum:
            - auto_approve
            - require_approval
            - deny
          example: require_approval
          description: >-
            Что решил `permissions.json` для этого действия. `deny` ВСЕГДА
            побеждает `allow` — даже автономия агента его не переопределяет.
        actor:
          description: Кто предложил.
          allOf:
            - $ref: '#/components/schemas/ActorResponseDto'
        decidedBy:
          type: object
          example: null
          nullable: true
          description: Id пользователя, который одобрил или отклонил. Никогда не агент.
        decidedAt:
          type: object
          example: null
          format: date-time
          nullable: true
        rejectionReason:
          type: object
          example: null
          nullable: true
        executionResult:
          type: object
          additionalProperties: true
          nullable: true
          example: null
          description: >-
            Результат выполнения, с формой, своей для каждого `actionType`, —
            вывод и код выхода для терминала, номер и URL PR для git-типов.
            `null`, пока действие не выполнено.
        createdAt:
          type: string
          example: '2026-07-27T14:33:10.900Z'
          format: date-time
        updatedAt:
          type: string
          example: '2026-07-27T14:33:10.900Z'
          format: date-time
        desfecho:
          type: string
          enum:
            - aprovada
            - ja_aprovada
          example: aprovada
          description: >-
            `aprovada`: этот клик одобрил действие. `ja_aprovada`: действие уже
            вышло из `pending` в результате одобрения (предыдущий клик, другой
            человек или политика) — идемпотентный успех, а не ошибка.
            ОТКЛОНЁННОЕ действие по-прежнему даёт 409 `acao_ja_recusada`.
        padraoGravado:
          type: boolean
          example: true
          description: >-
            Записал ли этот клик шаблон (и его событие `permission.granted`).
            `false` означает, что он уже существовал; ничего не записано.
      required:
        - id
        - projectId
        - sessionId
        - seq
        - actionType
        - payload
        - status
        - resolvedPolicy
        - actor
        - decidedBy
        - decidedAt
        - rejectionReason
        - executionResult
        - createdAt
        - updatedAt
        - desfecho
        - padraoGravado
    ArchitecturePendencyResponseDto:
      type: object
      properties:
        storyId:
          type: string
          example: 01JC4Z0000HISTORIA000000001
        title:
          type: string
          example: Add item to cart
        status:
          type: string
          example: ready
        reason:
          type: string
          enum:
            - no_module
            - missing_module
          example: missing_module
          description: >-
            `no_module`: история не указывает ни на один модуль.
            `missing_module`: она указывает на модуль, которого нет в текущей
            карте.
        missing:
          example:
            - pagamentos
          description: Упомянутые модули, которые не существуют. Пусто при `no_module`.
          type: array
          items:
            type: string
      required:
        - storyId
        - title
        - status
        - reason
        - missing
    ArchitectureResponseDto:
      type: object
      properties:
        moduleMap:
          nullable: true
          description: '`null`, пока Архитектор не написал первую карту.'
          type: object
          allOf:
            - $ref: '#/components/schemas/ModuleMapResponseDto'
        adrs:
          type: array
          items:
            $ref: '#/components/schemas/AdrRefResponseDto'
        pendencies:
          description: >-
            Перекрёстная проверка история↔карта. Пустой список — здоровое
            состояние.
          type: array
          items:
            $ref: '#/components/schemas/ArchitecturePendencyResponseDto'
        c4Diagram:
          $ref: '#/components/schemas/EstadoDoC4DiagramaResponseDto'
      required:
        - moduleMap
        - adrs
        - pendencies
        - c4Diagram
    ArchiveStoryDto:
      type: object
      properties:
        reason:
          type: string
          example: Duplicate of "Register a user".
          description: Необязательно; записывается в `backlog.story_archived`.
    ArchiveStoryInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        reason:
          type: string
          example: Duplicate of "Register a user".
      required:
        - projectId
    AreaDeAgentesResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
        projectId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
        key:
          type: string
          example: dev
          description: Идентификатор области. Уникален в пределах проекта.
        leadAgentId:
          type: string
          example: dev-lead
          description: Lead области — её единственный внешний контакт.
        maxParallel:
          type: number
          example: 2
          description: >-
            Сколько агентов lead может иметь в СЕССИИ без запроса разрешения.
            Сверх этого — `proposed_action`. Лимит на сессию, а не на модуль:
            подсчёт по модулю позволил бы N модулей × N агентов вообще без
            разрешения.
        budgetMicros:
          type: object
          example: 20000000
          nullable: true
          description: >-
            Лимит расходов области в микро-USD (ADR 0110, RN-443). `null`
            означает отсутствие лимита — независимая, ДОПОЛНИТЕЛЬНАЯ проверка
            рядом с бюджетами проекта и сессии, а не каскад: какой из трёх
            первым достигнет лимита, тот и блокирует вызов.
        spentMicros:
          type: number
          example: 4300000
          description: >-
            Накопленные расходы области в микро-USD. Увеличиваются всякий раз,
            когда тратит агент, принадлежащий этой области, С ЗАДАННЫМ ЛИМИТОМ
            ИЛИ БЕЗ.
        members:
          example:
            - dev-api
            - dev-web
          description: >-
            Участники. В области dev они берутся из `module_map`, по одному на
            модуль, — именно это не даёт области быть фиксированным списком в
            коде.
          type: array
          items:
            type: string
      required:
        - id
        - projectId
        - key
        - leadAgentId
        - maxParallel
        - budgetMicros
        - spentMicros
        - members
    ArquivosTocadosResponseDto:
      type: object
      properties:
        itens:
          description: Сначала самые свежие, не более 100.
          type: array
          items:
            $ref: '#/components/schemas/ArquivoTocadoResponseDto'
        total:
          type: number
          example: 140
          description: Реальный итог, даже когда `itens` обрезан.
        prsNaoLidas:
          type: number
          example: 0
          description: >-
            PR прочитанных сессий, файлы которых НЕ были прочитаны: сверх лимита
            в 20 PR или провайдер отклонил diff (RN-844).
      required:
        - itens
        - total
        - prsNaoLidas
    ArquivoTocadoResponseDto:
      type: object
      properties:
        actionId:
          type: string
          example: 01JC4Z0000ACAO00000000000001
        path:
          type: string
          example: src/app.ts
        additions:
          type: number
          example: 12
        deletions:
          type: number
          example: 3
      required:
        - actionId
        - path
        - additions
        - deletions
    ArtefatosDoAgenteResponseDto:
      type: object
      properties:
        actorId:
          type: string
          example: po
        quantos:
          type: number
          example: 7
          description: >-
            PR (`pr_open`/`open_adr_pr`) плюс эпики, истории и задачи, которые
            этот агент создал в сессии.
      required:
        - actorId
        - quantos
    AssignStoryModulesInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        storyId:
          type: string
          format: uuid
          example: 01JC4Z0000HISTORIA000000001
        moduleIds:
          example:
            - api
            - web
          description: >-
            Имена из текущего `module_map`. Несуществующий модуль становится
            незакрытым вопросом архитектуры.
          type: array
          items:
            type: string
      required:
        - projectId
        - storyId
        - moduleIds
    AttachLocalFolderRequestDto:
      type: object
      properties:
        folderName:
          type: string
          example: brabo
          description: Имя папки, выбранное в браузере, — показывается в панели покрытия.
        files:
          description: >-
            До 500 файлов. Точный лимит суммы байтов
            (RAG_LOCAL_TOTAL_BYTES_LIMIT) и список разрешённых расширений на
            файл применяются сценарием использования, а не этим DTO, — массив по
            отдельности допустимых строк всё равно может в сумме дать слишком
            большой payload.
          type: array
          items:
            $ref: '#/components/schemas/LocalFolderFileDto'
      required:
        - folderName
        - files
    AttachLocalFolderResponseDto:
      type: object
      properties:
        folderName:
          type: string
        filesIndexed:
          type: number
        filesSkipped:
          type: number
          description: >-
            Пропущены из-за превышения лимита байтов на файл или нераспознанного
            расширения — никогда не приводит к сбою всего пакета.
        chunksCreated:
          type: number
        embedding:
          $ref: '#/components/schemas/IndexEmbeddingResponseDto'
      required:
        - folderName
        - filesIndexed
        - filesSkipped
        - chunksCreated
        - embedding
    BindingResolvidoDaChaveResponseDto:
      type: object
      properties:
        key:
          type: string
          example: dev-lead
          description: Slug агента или ключ области, ровно как запрошено.
        binding:
          nullable: true
          description: >-
            ТО ЖЕ значение, которое отвечает маршрут для одного ключа, — `null`,
            когда ни на одном уровне каскада нет модели.
          type: object
          allOf:
            - $ref: '#/components/schemas/ResolvedBindingResponseDto'
      required:
        - key
        - binding
    BindingsResolvidosEmLoteResponseDto:
      type: object
      properties:
        agents:
          description: Одна запись на запрошенного агента, в порядке запроса.
          type: array
          items:
            $ref: '#/components/schemas/BindingResolvidoDaChaveResponseDto'
        areas:
          description: Одна запись на запрошенную область, в порядке запроса.
          type: array
          items:
            $ref: '#/components/schemas/BindingResolvidoDaChaveResponseDto'
      required:
        - agents
        - areas
    BlockTaskInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        agentId:
          type: string
          example: dev-api
        reason:
          type: string
          example: Gate correction cap reached.
        diagnosis:
          type: string
          example: Correction cycle exhausted without exit 0.
          description: >-
            Диагноз свободным текстом — человеческое описание произошедшего, а
            не структурированный источник сбоя.
        origin:
          type: string
          enum:
            - infra
            - modelo
            - codigo
            - politica
          example: infra
          description: >-
            ИСТОЧНИК сбоя: `infra`, `modelo`, `codigo` или `politica`. Никогда
            методом исключения — это урок из ADR 0020, поднятый на уровень выше
            QA Lead в ADR 0038. Необязательно: не каждое место вызова знает
            источник.
      required:
        - projectId
        - agentId
        - reason
        - diagnosis
    BootstrapDiagnosticResponseDto:
      type: object
      properties:
        kind:
          type: string
          enum:
            - missing_branch
            - unprotected_branch
            - missing_file
            - extra_branch
            - capability_unsupported
          example: extra_branch
          description: >-
            `extra_branch` — ИНФОРМАЦИОННОЕ и никогда не блокирует: у
            подключённого репозитория своя политика, и bootstrap её не удаляет.
        detail:
          type: object
          example:
            branchName: develop
            protected: false
          additionalProperties: true
      required:
        - kind
        - detail
    BootstrapPlanEstadoResponseDto:
      type: object
      properties:
        plan:
          nullable: true
          description: '`null`, когда у проекта нет подключённого репозитория.'
          type: object
          allOf:
            - $ref: '#/components/schemas/BootstrapPlanResponseDto'
        decision:
          type: string
          enum:
            - approved
            - as_is
          example: null
          nullable: true
          description: >-
            `null` = план сгенерирован и ещё НЕ решён. Это состояние, в котором
            ничего не выполняется (RN-045).
        decidedAt:
          type: object
          example: null
          nullable: true
          format: date-time
        decidedBy:
          type: object
          example: null
          nullable: true
      required:
        - plan
        - decision
        - decidedAt
        - decidedBy
    BootstrapPlanResponseDto:
      type: object
      properties:
        generatedAt:
          type: string
          example: '2026-08-01T23:45:00.000Z'
          format: date-time
        steps:
          description: >-
            Что СДЕЛАЛ БЫ bootstrap. Пустой список = репозиторий уже такой,
            каким его хочет шаблон, и одобрять нечего.
          type: array
          items:
            $ref: '#/components/schemas/BootstrapPlanStepResponseDto'
        diagnostics:
          description: Расхождения между репозиторием и шаблоном.
          type: array
          items:
            $ref: '#/components/schemas/BootstrapDiagnosticResponseDto'
      required:
        - generatedAt
        - steps
        - diagnostics
    BootstrapPlanStepResponseDto:
      type: object
      properties:
        step:
          type: string
          enum:
            - commit_pr_template
            - commit_branching_policy
            - commit_gitignore
            - create_dev_branch
            - create_qa_branch
            - create_rc_branch
            - protect_branches
          example: create_qa_branch
        actionType:
          type: string
          example: git_branch_create
          description: >-
            Та же таксономия, что и у `proposed_actions`, — каждый одобренный
            шаг становится записанным действием, когда выполняется bootstrap.
        payload:
          type: object
          example:
            branchName: qa
            fromRef: dev
          description: 'Данные изменения: какая ветка, какой файл, откуда.'
          additionalProperties: true
      required:
        - step
        - actionType
        - payload
    BudgetResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000ORCAMENTO000000001
        projectId:
          type: object
          example: 01JC4Z0000PROJETO0000000001
          nullable: true
          description: Заполнено для бюджета проекта; `null` для бюджета сессии.
        sessionId:
          type: object
          example: null
          nullable: true
          description: Противоположность вышеуказанному.
        limitMicros:
          type: number
          example: 50000000
          description: Лимит в микро-USD (50 USD).
        spentMicros:
          type: number
          example: 12400000
          description: Накопленные расходы в микро-USD.
        policy:
          type: string
          enum:
            - block
            - allow
          example: block
          description: >-
            `block` отклоняет вызов, когда он превысил бы лимит; `allow`
            пропускает его и только предупреждает.
        lastThresholdNotified:
          type: number
          example: 70
          description: >-
            Наивысший порог (70/90/100), о котором уже уведомлено. Нижней
            границей служит он, а не предыдущие расходы, — использование
            расходов заставило бы то же предупреждение срабатывать снова при
            каждом вызове после его пересечения.
        createdAt:
          type: string
          example: '2026-07-12T08:00:00.000Z'
          format: date-time
        updatedAt:
          type: string
          example: '2026-07-27T15:00:00.000Z'
          format: date-time
      required:
        - id
        - projectId
        - sessionId
        - limitMicros
        - spentMicros
        - policy
        - lastThresholdNotified
        - createdAt
        - updatedAt
    C4AtorInternalDto:
      type: object
      properties:
        name:
          type: string
          example: User
        type:
          type: string
          enum:
            - person
            - external_system
          default: person
          example: person
          description: >-
            `person` (по умолчанию) для того, кто управляет системой;
            `external_system` для другой системы, с которой она взаимодействует
            (например, Git-провайдер).
        description:
          type: string
          example: Whoever operates the product via the web.
      required:
        - name
    C4AtorResponseDto:
      type: object
      properties:
        name:
          type: string
          example: User
        type:
          type: string
          enum:
            - person
            - external_system
          example: person
        description:
          type: string
          example: Whoever operates the product through the web.
      required:
        - name
        - type
        - description
    C4DiagramaGeradoResponseDto:
      type: object
      properties:
        diagrama:
          $ref: '#/components/schemas/C4DiagramaResponseDto'
        version:
          type: number
          example: 1
      required:
        - diagrama
        - version
    C4DiagramaResponseDto:
      type: object
      properties:
        systemName:
          type: string
          example: Brabo
        systemDescription:
          type: string
          example: Agent-orchestrated engineering platform.
        actors:
          type: array
          items:
            $ref: '#/components/schemas/C4AtorResponseDto'
        contextDiagram:
          type: string
          example: |-
            C4Context
              title Context Diagram -- Brabo
              ...
          description: Синтаксис Mermaid `C4Context` — система и внешние акторы.
        containerDiagram:
          type: string
          example: |-
            C4Container
              title Container Diagram -- Brabo
              ...
          description: >-
            Синтаксис Mermaid `C4Container` — модули текущего module_map и
            зависимости между ними.
      required:
        - systemName
        - systemDescription
        - actors
        - contextDiagram
        - containerDiagram
    ChatSseEventResponseDto:
      type: object
      properties:
        type:
          type: string
          enum:
            - delta
            - done
            - error
            - metering_failed
          example: delta
          description: >-
            `delta` несёт инкрементальный `text`; `done` закрывает поток с
            учётом токенов/стоимости; `error` прерывает; `metering_failed`
            означает, что ОТВЕТ ушёл, но стоимость не была учтена, — кадр
            существует, чтобы этот сбой не остался незамеченным.
        text:
          type: string
          example: I will open the epic
          description: Только в `delta`.
        inputTokens:
          type: number
          example: 1820
          description: Только в `done`.
        outputTokens:
          type: number
          example: 340
          description: Только в `done`.
        costMicros:
          type: number
          example: 52700
          description: Только в `done`, микро-USD.
        estimated:
          type: boolean
          example: false
          description: >-
            Только в `done`. `true`, когда провайдер не вернул счётчик и
            стоимость была ОЦЕНЕНА, — число стоит меньше, и интерфейс должен
            уметь об этом сказать.
        message:
          type: string
          example: session budget exhausted
          description: Только в `error` и `metering_failed`.
      required:
        - type
    CicloDeVidaDoContainerResponseDto:
      type: object
      properties:
        status:
          type: string
          enum:
            - provisioning
            - running
            - stopped
            - failed
            - removed
          example: provisioning
          description: >-
            То, что было ЗАПИСАНО, а не то, что подтверждает демон Docker, —
            сегодня ни один реальный оркестратор не переводит эту таблицу
            (RN-243).
        imageVersion:
          type: number
          example: 1
          description: >-
            Версия `artifact.project_image`, ЗАМОРОЖЕННАЯ при первом переходе
            (RN-245), — не применяется заново при каждом чтении.
        resources:
          description: >-
            Лимит, ОБЪЯВЛЕННЫЙ при создании, — не применяется принудительно
            (RN-248).
          allOf:
            - $ref: '#/components/schemas/RecursosDoContainerResponseDto'
        failureReason:
          type: object
          nullable: true
          example: null
          description: Заполняется только при переходе в `failed`.
        createdAt:
          type: string
          format: date-time
        statusChangedAt:
          type: string
          format: date-time
        observado:
          nullable: true
          description: >-
            То, что демон Docker сообщает ПРЯМО СЕЙЧАС, по запросу через broker
            (ADR 0130), — никогда не сливается с полями выше, которые являются
            ЗАПИСАННЫМ. Контейнер, убитый извне, отображается как записанный
            `running` и наблюдаемый `exited`, и в этом весь смысл. `null`
            означает либо «контейнера нет», либо «не удалось посмотреть» —
            читайте `naoObservado`, чтобы их различить.
          type: object
          allOf:
            - $ref: '#/components/schemas/ObservacaoDeContainerResponseDto'
        naoObservado:
          type: string
          enum:
            - broker-nao-configurado
            - broker-sem-resposta
            - broker-recusou
            - null
          nullable: true
          example: broker-nao-configurado
          description: >-
            `null`, когда наблюдение действительно СОСТОЯЛОСЬ, — в том числе
            когда оно вернулось пустым, что является положительным утверждением
            «я посмотрел, и контейнера нет». Заполняется, когда посмотреть не
            удалось, с указанием причины. Наследовать здесь записанное состояние
            — именно то, что запрещает RN-468.
        detalheDaObservacao:
          type: object
          nullable: true
          example: null
          description: Собственное сообщение broker, если оно было.
      required:
        - status
        - imageVersion
        - resources
        - failureReason
        - createdAt
        - statusChangedAt
        - observado
        - naoObservado
        - detalheDaObservacao
    ClaimTaskInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        module:
          type: string
          example: api
          description: В выбор попадают только задачи этого модуля.
        agentId:
          type: string
          example: dev-api
      required:
        - projectId
        - module
        - agentId
    CloseAnamneseWindowInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        windowFrom:
          type: string
          example: '2026-07-27T12:00:00.000Z'
          format: date-time
        windowTo:
          type: string
          example: '2026-07-27T12:15:00.000Z'
          format: date-time
        eventCount:
          type: number
          example: 12
      required:
        - projectId
        - windowFrom
        - windowTo
        - eventCount
    CodeBlameLineResponseDto:
      type: object
      properties:
        line:
          type: number
          example: 12
          description: С 1, как показывает любой редактор.
        commitSha:
          type: string
          example: 0f3e8181fbd010b10c78db17b90ecb35fb8cc89c
        author:
          type: string
          example: Daniel Souza
        authorDate:
          type: string
          example: '2026-08-04T12:00:00.000Z'
        summary:
          type: string
          example: 'fix(api): fix the credential resolver'
      required:
        - line
        - commitSha
        - author
        - authorDate
        - summary
    CodeBlameResponseDto:
      type: object
      properties:
        ref:
          type: string
          example: dev
        path:
          type: string
          example: apps/api/src/main.ts
        lines:
          type: array
          items:
            $ref: '#/components/schemas/CodeBlameLineResponseDto'
        truncated:
          type: boolean
          example: false
          description: Файл превысил лимит аннотируемых строк на вызов.
      required:
        - ref
        - path
        - lines
        - truncated
    CodeBranchDetailListResponseDto:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/CodeBranchDetailResponseDto'
        truncated:
          type: boolean
          example: false
          description: Список обрезан на лимите обогащённых веток.
      required:
        - items
        - truncated
    CodeBranchDetailResponseDto:
      type: object
      properties:
        name:
          type: string
          example: feature/x
        commitSha:
          type: string
          example: 0f3e8181fbd010b10c78db17b90ecb35fb8cc89c
        protected:
          type: boolean
          example: false
        ahead:
          type: number
          nullable: true
          example: 3
          description: Коммиты впереди ветки по умолчанию. `null`, когда не вычисляется.
        behind:
          type: number
          nullable: true
          example: 0
          description: Коммиты позади ветки по умолчанию.
        pullRequest:
          nullable: true
          type: object
          allOf:
            - $ref: '#/components/schemas/CodeBranchPullRequestRefResponseDto'
        producedBy:
          nullable: true
          description: >-
            Dev agent/модуль, владеющий веткой (RN-152), когда имя соответствует
            `feature/task-XXXXXXXX` и задача/модуль ещё разрешимы. `null` для
            ручной ветки пользователя или для `main`/`dev`/`qa`.
          type: object
          allOf:
            - $ref: '#/components/schemas/CodeBranchProducedByResponseDto'
      required:
        - name
        - commitSha
        - protected
        - ahead
        - behind
        - pullRequest
        - producedBy
    CodeBranchProducedByResponseDto:
      type: object
      properties:
        agentId:
          type: string
          example: dev-pieces
          description: >-
            agent_id того dev, который создал ветку
            (`dev-<module>`/`dev-<module>-2`, RN-087).
        moduleId:
          type: string
          example: pieces
          description: Имя модуля, из текущего `module_map` проекта.
      required:
        - agentId
        - moduleId
    CodeBranchPullRequestRefResponseDto:
      type: object
      properties:
        number:
          type: number
          example: 42
        state:
          type: string
          enum:
            - open
            - merged
            - closed
          example: open
      required:
        - number
        - state
    CodeDiffFileResponseDto:
      type: object
      properties:
        path:
          type: string
          example: apps/api/src/main.ts
          description: Путь ПОСЛЕ изменения (для `removed` — то, что исчезло).
        previousPath:
          type: string
          nullable: true
          example: null
          description: Прежний путь; заполняется, только когда `status` равен `renamed`.
        status:
          type: string
          enum:
            - added
            - modified
            - removed
            - renamed
          example: modified
        additions:
          type: number
          example: 12
        deletions:
          type: number
          example: 3
        patch:
          type: string
          nullable: true
          example: |
            @@ -1,3 +1,4 @@
            +import x
          description: >-
            Unified diff файла. `null`, когда провайдер его не передаёт
            (бинарный файл или слишком большой патч), — в отличие от `""`, что
            означает «вернулся пустым». Без этого различия экран говорил бы «нет
            изменений» про изменённый бинарный файл.
      required:
        - path
        - previousPath
        - status
        - additions
        - deletions
        - patch
    CodeDiffResponseDto:
      type: object
      properties:
        pullRequestId:
          type: string
          example: pr-1
        files:
          type: array
          items:
            $ref: '#/components/schemas/CodeDiffFileResponseDto'
        truncated:
          type: boolean
          example: false
          description: Список обрезан на лимите файлов на diff.
      required:
        - pullRequestId
        - files
        - truncated
    CodeFileResponseDto:
      type: object
      properties:
        ref:
          type: string
          example: dev
        path:
          type: string
          example: apps/api/src/main.ts
        content:
          type: string
          example: |
            import { NestFactory } from '@nestjs/core';
          description: Содержимое UTF-8. Бинарные файлы этим маршрутом не отдаются.
        truncated:
          type: boolean
          example: false
          description: >-
            Файл превысил лимит байтов, и `content` — его начало. Вкладка только
            для чтения: обрезать и предупредить лучше, чем отказать в файле.
        bytes:
          type: number
          example: 1284
          description: ВОЗВРАЩЁННЫЕ байты — после обрезки, а не до.
      required:
        - ref
        - path
        - content
        - truncated
        - bytes
    CodePullRequestListResponseDto:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/CodePullRequestSummaryResponseDto'
        truncated:
          type: boolean
          example: false
          description: Список обрезан на лимите PR на вызов.
      required:
        - items
        - truncated
    CodePullRequestSummaryResponseDto:
      type: object
      properties:
        id:
          type: string
          example: '2401938475'
        number:
          type: number
          example: 42
        title:
          type: string
          example: 'fix(api): the agents area is born with the project'
        url:
          type: string
          example: https://github.com/acme/repo/pull/42
        author:
          type: string
          nullable: true
          example: daneiel
          description: >-
            Логин/имя пользователя того, кто открыл. `null`, когда провайдер
            этого не сообщает.
        state:
          type: string
          enum:
            - open
            - merged
            - closed
          example: open
        sourceBranch:
          type: string
          example: feature/x
        targetBranch:
          type: string
          example: dev
        updatedAt:
          type: string
          nullable: true
          example: '2026-08-04T12:00:00.000Z'
          description: '`null`, когда провайдер этого не сообщает.'
      required:
        - id
        - number
        - title
        - url
        - author
        - state
        - sourceBranch
        - targetBranch
        - updatedAt
    CodeSearchMatchResponseDto:
      type: object
      properties:
        path:
          type: string
          example: apps/api/src/main.ts
        line:
          type: number
          example: 12
          description: С 1, как показывает любой редактор.
        text:
          type: string
          example: '  const app = await NestFactory.create(AppModule);'
          description: >-
            Вся строка, укороченная, чтобы минифицированный файл никогда не
            возвращался целиком.
      required:
        - path
        - line
        - text
    CodeSearchResponseDto:
      type: object
      properties:
        ref:
          type: string
          example: dev
        path:
          type: string
          example: ''
          description: Поддерево, по которому выполнялся поиск.
        query:
          type: string
          example: NestFactory
        matches:
          type: array
          items:
            $ref: '#/components/schemas/CodeSearchMatchResponseDto'
        filesScanned:
          type: number
          example: 87
          description: >-
            Фактически открытые файлы. Это реальная стоимость поиска, и она в
            ответе, чтобы не оставаться невидимой.
        truncated:
          type: boolean
          example: true
          description: >-
            Сканирование остановилось по бюджету (каталоги, файлы или
            совпадения), не пройдя дерево до конца. Сузьте `path` или запрос.
      required:
        - ref
        - path
        - query
        - matches
        - filesScanned
        - truncated
    CodeTreeEntryResponseDto:
      type: object
      properties:
        path:
          type: string
          example: apps/api/src/main.ts
          description: Полный путь от корня репозитория.
        name:
          type: string
          example: main.ts
          description: Последний сегмент `path`.
        type:
          type: string
          enum:
            - file
            - dir
          example: file
        size:
          type: number
          nullable: true
          example: 1284
          description: >-
            Байты. `null` для каталога и когда провайдер этого не сообщает —
            отличать это от `0` важно, чтобы экран никогда не утверждал «пустой
            файл».
      required:
        - path
        - name
        - type
        - size
    CodeTreeResponseDto:
      type: object
      properties:
        ref:
          type: string
          example: dev
          description: Прочитанная ветка, тег или sha.
        path:
          type: string
          example: apps/api
          description: Перечисленный каталог; `""` — корень.
        entries:
          type: array
          items:
            $ref: '#/components/schemas/CodeTreeEntryResponseDto'
        truncated:
          type: boolean
          example: false
          description: >-
            Список обрезан на лимите записей на уровень. В каталоге больше, чем
            вернулось здесь.
      required:
        - ref
        - path
        - entries
        - truncated
    CompleteStoryInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        description:
          type: string
          example: As a buyer, I want to pay by card.
        rf:
          example:
            - The cart accepts up to 50 items
          type: array
          items:
            type: string
        rnf:
          example:
            - Response under 200ms at p95
          type: array
          items:
            type: string
        dod:
          example:
            - Unit tests green
          type: array
          items:
            type: string
        dor:
          example:
            - Module defined
          type: array
          items:
            type: string
        businessRuleIds:
          example:
            - RN-014
          description: >-
            ДОБАВЛЯЕТСЯ к правилам, на которые история уже ссылается (дополнение
            никогда не отвязывает правило); каждый новый id должен ссылаться на
            существующий `artifact.business_rule`.
          type: array
          items:
            type: string
        questionId:
          type: string
          format: uuid
          description: >-
            RN-859 (ADR 0214): открытый вопрос, на который пользователь УЖЕ
            ответил, — только тогда можно исправить историю `ready` (без задачи
            в работе).
      required:
        - projectId
    ConfirmacaoDeArquiteturaResponseDto:
      type: object
      properties:
        ok:
          type: boolean
          example: true
          enum:
            - true
        desfecho:
          type: string
          enum:
            - confirmado
            - ja_oferecido
          description: >-
            `confirmado`: Infra запущен. `ja_oferecido`: у него уже было
            ожидающее предложение или он был активен в проекте — ничего не
            записано и у engine ничего не запрошено (двойной клик, вторая
            вкладка). Dev Lead здесь больше не цель: Infra предлагает его, когда
            контейнер становится `running` (RN-672).
        jaAtendidos:
          type: array
          items:
            $ref: '#/components/schemas/AlvoJaAtendidoResponseDto'
      required:
        - ok
        - desfecho
        - jaAtendidos
    ConfirmProjectWorkspaceInternalDto:
      type: object
      properties:
        path:
          type: string
          example: /home/you/projects/store
          description: >-
            Абсолютный путь, подтверждённый runner НА ХОСТЕ, — источник истины
            (RN-423). Здесь перед записью ЛЕКСИЧЕСКИ перепроверяется;
            недопустимый — 400, никогда не записывается.
        sessionId:
          type: string
          format: uuid
          example: 01JC4Z0000SESSAO00000000001
          description: >-
            Самая свежая сессия проекта (`ProjectSession.latest_id/1`),
            определённая на стороне engine. `null`/опущено, когда у проекта ещё
            нет сессий, — проект всё равно обновляется, пропускается только
            событие аудита (принятая лакуна, RN-423).
        actorId:
          type: string
          format: uuid
          example: 01JC4Z0000USUARIO0000000001
          description: >-
            Кто запросил тикет runner (владелец сокета, который подтвердил), —
            становится актором события `project.workspace_verified`. Если
            опущено: событие (если есть сессия) рождается без
            идентифицированного актора.
      required:
        - path
    ConfirmProjectWorkspaceResponseDto:
      type: object
      properties:
        verified:
          type: boolean
          example: true
          description: >-
            Всегда `true`, когда вызов не выбросил исключение, — подтверждение
            идемпотентно. `changed` говорит, изменилось ли ЧТО-НИБУДЬ в этом
            вызове.
        workspacePath:
          type: string
          example: /home/you/projects/store
          description: >-
            ЗАПИСАННЫЙ (нормализованный) путь — может отличаться от того, что
            был при создании, потому что runner — источник истины и
            перезаписывает его.
        changed:
          type: boolean
          example: true
          description: >-
            `true` при первом подтверждении или когда сообщённый путь ОТЛИЧАЕТСЯ
            от уже записанного. `false` при переподключении, сообщающем тот же
            путь, что и всегда, — ничего не перезаписано.
      required:
        - verified
        - workspacePath
        - changed
    ContainerExecInternalDto:
      type: object
      properties:
        comando:
          type: string
          example: npm test
          description: >-
            Команда оболочки, интерпретируемая `sh -c` ВНУТРИ контейнера, — то
            же соглашение, что и у действия терминала, выполняемого вне его.
        cwd:
          type: string
          example: /work/.worktrees/dev-api
          description: >-
            Рабочий каталог ВНУТРИ контейнера. Уже переведён из пути хоста
            engine (RN-492) — этот DTO не повторяет этот перевод. Если опущен,
            выполняется в `/work`, корне проекта. Broker перепроверяет, что он
            внутри `/work`, и иначе отклоняет (`DiretorioForaDoEscopoError`).
        timeoutMs:
          type: number
          example: 300000
          description: >-
            Таймаут в миллисекундах. Если опущен, используется значение broker
            по умолчанию.
        alvo:
          description: >-
            AT-612: run in the task QA instance instead of the work container.
            Set by the engine from the gate context, never by the model.
          allOf:
            - $ref: '#/components/schemas/AlvoQaInternalDto'
      required:
        - comando
    ContainerExecInternalResponseDto:
      type: object
      properties:
        sucesso:
          type: boolean
          example: true
          description: >-
            `false`, когда broker отказал или не ответил, — остальные поля
            отсутствуют, а `motivo` объясняет.
        exitCode:
          type: number
          example: 0
          description: 'Присутствует только при `sucesso: true`.'
        output:
          type: string
          example: |
            ok
          description: 'Присутствует только при `sucesso: true`.'
        timedOut:
          type: boolean
          example: false
          description: 'Присутствует только при `sucesso: true`.'
        motivo:
          type: string
          example: 'o broker de container não respondeu em http://broker:8090: timeout'
          description: 'Присутствует только при `sucesso: false`.'
      required:
        - sucesso
    ContainerOverviewItemResponseDto:
      type: object
      properties:
        projectId:
          type: string
          example: 01JC4Z0000PROJETO000001
        projectName:
          type: string
          example: exp002
        projectSlug:
          type: string
          example: exp002
        executionMode:
          type: string
          enum:
            - container
            - mounted
            - runner
          example: runner
          description: >-
            Где поднимается контейнер этого проекта (RN-497/503). `container` и
            `mounted` идут через broker, на сервере; `runner` — через локального
            агента на машине пользователя. Именно это разветвляет действие
            запуска между `container_start` и `container_start_via_runner`.
        registrado:
          nullable: true
          description: >-
            `null` означает, что этот проект НИКОГДА не создавал контейнер, —
            ТРЕТЬЕ состояние, а не значение `status`: это ни `stopped`
            (контейнер, который существовал и остановился), ни «не удалось
            наблюдать» (что касается демона). Читайте вместе с этим
            `naoVerificado: "sem_container_registrado"`.
          type: object
          allOf:
            - $ref: '#/components/schemas/RegistroDeContainerResponseDto'
        temImagemDecidida:
          type: boolean
          example: true
          description: >-
            Существует ли ХОТЬ ОДИН `artifact.project_image` для этого проекта —
            gate RN-105, который начиная с RN-494/ADR 0135 применяется ко всем
            ТРЁМ режимам. `false` означает, что запустить контейнер сейчас
            невозможно, и страница говорит об этом, а не предлагает действие,
            заведомо обречённое на неудачу.
        workspaceVerifiedAt:
          type: object
          nullable: true
          format: date-time
          description: >-
            Когда runner ПОДТВЕРДИЛ папку (RN-423), — имеет смысл только для
            `executionMode: "runner"`. Записывает ОДНО подтверждение и никогда
            не является heartbeat (RN-468): ненулевое значение НЕ доказывает,
            что локальный агент подключён сейчас. Доказывает оно отрицательное —
            `null` у проекта `runner` означает, что ни один runner никогда не
            подключался.
        observado:
          nullable: true
          description: >-
            То, что broker сообщает прямо сейчас, — `null` либо потому, что
            контейнера нет, либо потому, что спросить не удалось
            (`naoObservado`), либо потому, что эту строку в эту загрузку не
            спрашивали (`naoVerificado`).
          type: object
          allOf:
            - $ref: '#/components/schemas/ObservacaoDeContainerResponseDto'
        naoObservado:
          type: string
          enum:
            - broker-nao-configurado
            - broker-sem-resposta
            - broker-recusou
            - null
          nullable: true
        detalheDaObservacao:
          type: object
          nullable: true
          example: null
        naoVerificado:
          type: string
          enum:
            - fora_do_escopo_da_verificacao
            - teto_de_verificacoes_atingido
            - sem_container_registrado
            - null
          nullable: true
          example: null
          description: >-
            Ненулевое, когда эту строку в эту загрузку НЕ спрашивали у broker, —
            никогда не путается с `naoObservado`, что означает, что спрашивали и
            не удалось. `fora_do_escopo_da_verificacao`: статус
            `stopped`/`failed`/`removed`, где подтверждение демона не важно.
            `teto_de_verificacoes_atingido`: подходит, но бюджет вызовов broker
            на загрузку уже израсходован другими строками.
            `sem_container_registrado`: проект его никогда не создавал, поэтому
            наблюдать нечего, — и он тоже никогда не тратит вызов из бюджета.
        acaoPendente:
          nullable: true
          description: >-
            Ожидающее действие
            `container_start`/`container_stop`/`container_remove`/`container_start_via_runner`
            для этого проекта, если есть, — в ЛЮБОЙ из его сессий. Страница
            отображает для него встроенный `ApprovalCard` вместо кнопки
            действия, по тому же шаблону, что и вкладка PR.
          type: object
          allOf:
            - $ref: '#/components/schemas/ProposedActionResponseDto'
        decisaoVigente:
          type: object
          nullable: true
          example:
            version: 3
            image: node:22
            network: egress
          description: The current (highest version) image decision, or null.
        imagemDesatualizada:
          type: boolean
          example: false
          description: >-
            The registered container (running/stopped/provisioning) differs in
            CONTENT (image, network, resources) from the current decision; only
            recreating it (stop, remove, start) applies the new one.
        brokerConfigurado:
          type: boolean
          example: false
          description: >-
            Настроен ли у ЭТОЙ УСТАНОВКИ broker контейнеров (`BROKER_URL` задан,
            ADR 0130), — одно и то же значение в каждой строке, потому что это
            конфигурация установки, а не свойство проекта. Проекты `container` и
            `mounted` поднимают контейнер только через broker (ADR 0144),
            поэтому при `false` страница отказывает им в кнопке запуска ДО клика
            и говорит почему (ADR 0161, RN-574); проекты `runner` не
            затрагиваются. Говорит, что переменная существует, и никогда — что
            broker отвечает: это сообщает `naoObservado`.
      required:
        - projectId
        - projectName
        - projectSlug
        - executionMode
        - registrado
        - temImagemDecidida
        - workspaceVerifiedAt
        - observado
        - naoObservado
        - detalheDaObservacao
        - naoVerificado
        - acaoPendente
        - decisaoVigente
        - imagemDesatualizada
        - brokerConfigurado
    ContainerSpecInternalResponseDto:
      type: object
      properties:
        projectId:
          type: string
          example: f52be111-0000-4000-8000-000000000000
        projectSlug:
          type: string
          example: exp002
        workspaceId:
          type: string
          example: aaaaaaaa-0000-4000-8000-000000000000
        workspaceDirName:
          type: string
          example: exp002-f52be111
          description: >-
            Имя папки, ЗАМОРОЖЕННОЕ при создании проекта (RN-109). Это
            единственный источник имени контейнера (`brabo-<workspaceDirName>`)
            во ВСЕХ трёх режимах. Где находится ПАПКА — другой вопрос, на
            который отвечает `localizacao` (RN-503), — api по-прежнему никогда
            не отправляет абсолютный путь, потому что путь изнутри контейнера
            api — это не путь, который может разрешить демон Docker.
        executionMode:
          type: string
          enum:
            - container
            - mounted
            - runner
          example: container
          description: >-
            Где живёт код. Broker обслуживает `container` И `mounted` (RN-503):
            начиная с ADR 0141 смонтированная папка живёт под одной базой,
            которую этот сервер монтирует по идентичности, поэтому демон до неё
            достаёт. Он по-прежнему отклоняет `runner`: эта папка на машине
            пользователя, и ни один корень здесь её не видит, — там контейнер
            поднимает runner (ADR 0137).
        localizacao:
          description: >-
            Дискриминированный локатор папки проекта (RN-503): какой из двух
            корней broker её разрешает и относительный сегмент, который нужно
            присоединить к этому корню.
          allOf:
            - $ref: '#/components/schemas/LocalizacaoDoProjetoResponseDto'
        usuarioDaPasta:
          type: object
          nullable: true
          properties:
            uid:
              type: integer
              example: 1000
            gid:
              type: integer
              example: 1000
          description: >-
            ВЛАДЕЛЕЦ папки проекта, измеренный api (ADR 0180): broker запускает
            контейнер с `--user uid:gid`, чтобы dev agent мог писать в папку при
            `--cap-drop ALL`. `null`, когда папку невозможно измерить, проект
            `runner` или владелец — root, — тогда контейнер запускается как
            раньше. Никогда не принимается из запроса к broker.
        imagem:
          nullable: true
          description: >-
            `null`, пока Архитектор не принял решение (RN-105), — тогда `start`
            отклоняется с 409 на стороне broker, а остальные четыре операции
            продолжают работать.
          type: object
          allOf:
            - $ref: '#/components/schemas/ImagemParaOBrokerResponseDto'
        imagemVersao:
          type: number
          example: 3
          description: >-
            Версия текущего артефакта; 0, когда решения нет. Попадает в
            контейнер как метка `brabo.image.version`.
        comandoDoApp:
          type: string
          nullable: true
          description: >-
            AT-610 (ADR 0228): the app command declared by the Infra in
            `run.app` (`artifact.run_instructions`), or `null`. Always `null`
            for `runner`.
        ports:
          type: array
          nullable: true
          items:
            type: object
            properties:
              host:
                type: integer
                example: 8080
              container:
                type: integer
                example: 3000
              protocol:
                type: string
                example: tcp
          description: >-
            AT-608: ports declared in `artifact.container_network`, published on
            127.0.0.1 only. `null` when undeclared or `runner`.
        dns:
          nullable: true
          description: 'AT-608: DNS servers (literal IPs), or `null`.'
          type: array
          items:
            type: string
        extraHosts:
          type: array
          nullable: true
          items:
            type: object
            properties:
              name:
                type: string
                example: api.local
              ip:
                type: string
                example: 10.0.0.5
          description: 'AT-608: `--add-host` entries (IPv4), or `null`.'
        qa:
          type: object
          properties:
            chave:
              type: string
              example: a1b2c3d4
            services:
              type: array
              items:
                type: object
          description: >-
            AT-612 (ADR 0229): ONLY with `papel=qa`. `chave` = 8 first [a-z0-9]
            of the task id; `services` = `run.qa.services` declared by the Infra
            (image with digest, env, tmpfs, user `<uid>:<gid>` never 0, command
            argv after the image, shared).
      required:
        - projectId
        - projectSlug
        - workspaceId
        - workspaceDirName
        - executionMode
        - localizacao
        - usuarioDaPasta
        - imagem
        - imagemVersao
        - comandoDoApp
        - ports
        - dns
        - extraHosts
    ContratoDeModuloInternalDto:
      type: object
      properties:
        modulo:
          type: string
          example: board-engine
          description: Имя модуля — должно существовать в текущем module_map.
        expoe:
          description: Что модуль предоставляет тем, кто от него зависит. От 1 до 40.
          type: array
          items:
            $ref: '#/components/schemas/ItemDeContratoInternalDto'
      required:
        - modulo
        - expoe
    ContratoDeModuloResponseDto:
      type: object
      properties:
        modulo:
          type: string
          example: board-engine
        expoe:
          type: array
          items:
            $ref: '#/components/schemas/ItemDeContratoResponseDto'
      required:
        - modulo
        - expoe
    ContratoLidoDeModuloResponseDto:
      type: object
      properties:
        modulo:
          type: string
          example: game-session
        dependeDe:
          example:
            - board-engine
            - scoring
          description: '`dependsOn` текущего module_map: что этот модуль ПОТРЕБЛЯЕТ.'
          type: array
          items:
            type: string
        expoe:
          nullable: true
          description: >-
            Что он предоставляет. `null` = Архитектор не объявил контракт для
            этого модуля (отличается от «ничего не предоставляет»).
          type: array
          items:
            $ref: '#/components/schemas/ItemDeContratoResponseDto'
      required:
        - modulo
        - dependeDe
        - expoe
    ContratosDeclaradosResponseDto:
      type: object
      properties:
        contratos:
          type: array
          items:
            $ref: '#/components/schemas/ContratoDeModuloResponseDto'
        version:
          type: number
          example: 1
      required:
        - contratos
        - version
    ContratosDoProjetoResponseDto:
      type: object
      properties:
        status:
          type: string
          enum:
            - sem_contratos
            - declarados
        version:
          type: number
          example: 2
          description: 0, когда контракта нет.
        modulos:
          description: По одному на модуль ТЕКУЩЕГО module_map, в порядке карты.
          type: array
          items:
            $ref: '#/components/schemas/ContratoLidoDeModuloResponseDto'
        contratosForaDoMapa:
          example: []
          description: >-
            Контракты модулей, которых больше нет в текущем module_map, —
            указаны, но никогда не привязаны к несуществующему модулю.
          type: array
          items:
            type: string
      required:
        - status
        - version
        - modulos
        - contratosForaDoMapa
    ConvertExecutionModeDto:
      type: object
      properties:
        executionMode:
          type: string
          enum:
            - container
            - mounted
            - runner
          example: runner
          description: >-
            Новый режим выполнения. Может повторять текущий режим проекта — в
            этом случае реально меняется только `workspacePath`.
        workspacePath:
          type: string
          example: /home/you/projects/store
          description: >-
            Абсолютный путь к папке пользователя — обязателен, когда
            `executionMode` равен `mounted` или `runner`, и отклоняется, когда
            он `container`. Та же проверка, что и при создании
            (RN-170/RN-422/RN-423): в `mounted` он должен существовать и быть
            доступен для записи изнутри контейнера; в `runner` сейчас
            проверяется только формат — настоящее подтверждение приходит от
            повторного подключения runner.
      required:
        - executionMode
    CoverageReportResponseDto:
      type: object
      properties:
        rules:
          type: array
          items:
            $ref: '#/components/schemas/RuleCoverageResponseDto'
        uncoveredCount:
          type: number
          example: 2
          description: Сколько правил вообще не имеют истории. Каждое — открытие.
      required:
        - rules
        - uncoveredCount
    CreateActionInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        actionType:
          type: string
          enum:
            - terminal
            - git_commit
            - git_push
            - pr_open
            - spend
            - git_repo_create
            - git_branch_create
            - git_branch_protect
            - write_file
            - open_adr_pr
            - git_merge
            - open_infra_pr
            - instruction_patch
            - parallelize
            - raise_max_parallel
            - propose_execution_plan
            - assess_implementability
            - container_start
            - container_stop
            - container_remove
            - container_recreate
            - container_start_via_runner
            - qa_environment_start
          example: terminal
        actor:
          description: На этом маршруте всегда агент.
          allOf:
            - $ref: '#/components/schemas/ActorDto'
        payload:
          type: object
          additionalProperties: true
          example:
            command: pnpm test
      required:
        - projectId
        - actionType
        - actor
        - payload
    CreateC4DiagramInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
        systemName:
          type: string
          example: Brabo
          description: Имя системы/проекта для подписи диаграммы.
        systemDescription:
          type: string
          example: Agent-orchestrated engineering platform.
        actors:
          description: >-
            Внешние акторы уровня Context (Simon Brown). Контейнеры уровня
            Container сюда НЕ входят: они берутся из текущего module_map
            проекта.
          type: array
          items:
            $ref: '#/components/schemas/C4AtorInternalDto'
      required:
        - projectId
        - systemName
    CreatedStoryResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000HISTORIA000000001
        epicId:
          type: string
          example: 01JC4Z0000EPICO000000000001
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        sessionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
        title:
          type: string
          example: Add item to cart
        description:
          type: string
          example: As a buyer, I want to gather items before paying.
        rf:
          example:
            - The cart accepts up to 50 items
          description: Функциональные требования.
          type: array
          items:
            type: string
        rnf:
          example:
            - The response stays under 200 ms at p95
          description: Нефункциональные требования.
          type: array
          items:
            type: string
        businessRuleIds:
          example:
            - RN-014
          description: >-
            Покрытые бизнес-правила. Именно отсюда вычисляется покрытие
            правило→история в `GET /projects/:id/coverage`.
          type: array
          items:
            type: string
        dod:
          example:
            - Unit tests green
          description: Критерии готовности (Definition of done).
          type: array
          items:
            type: string
        dor:
          example:
            - Module defined
          description: Критерии готовности к работе (Definition of ready).
          type: array
          items:
            type: string
        moduleIds:
          example:
            - api
          description: >-
            Модули из `module_map`, которых касается история. История без модуля
            или с несуществующим модулем становится незакрытым вопросом
            архитектуры.
          type: array
          items:
            type: string
        status:
          type: string
          enum:
            - draft
            - ready
            - in_progress
            - done
          example: ready
        proposedReady:
          type: boolean
          example: true
          description: >-
            PO завершил историю, и она ждёт решения пользователя (Фаза 12c —
            RN-048). Сосуществует со `status: "draft"`: это предложение, а не
            состояние. Всегда `false` в проекте в режиме `auto`.
        returnedReason:
          type: object
          example: DoD too generic — spell out the acceptance criteria.
          nullable: true
          description: >-
            Почему пользователь вернул историю PO. `null`, когда её никогда не
            возвращали.
        returnedAt:
          type: object
          example: '2026-08-02T14:00:00.000Z'
          format: date-time
          nullable: true
        archivedAt:
          type: string
          example: null
          format: date-time
          description: >-
            Когда история была АРХИВИРОВАНА (RN-727). Архивные истории уходят из
            backlog, покрытия, плана и claim; `null` — история в игре.
          nullable: true
        archivedReason:
          type: string
          example: null
          description: Почему она была архивирована, если кто-то сказал (RN-727).
          nullable: true
        createdAt:
          type: string
          example: '2026-07-25T09:00:00.000Z'
          format: date-time
        updatedAt:
          type: string
          example: '2026-07-25T09:40:00.000Z'
          format: date-time
        semanticDuplicate:
          $ref: '#/components/schemas/SemanticDuplicateCheckResponseDto'
      required:
        - id
        - epicId
        - projectId
        - sessionId
        - title
        - description
        - rf
        - rnf
        - businessRuleIds
        - dod
        - dor
        - moduleIds
        - status
        - proposedReady
        - returnedReason
        - returnedAt
        - archivedAt
        - archivedReason
        - createdAt
        - updatedAt
        - semanticDuplicate
    CreateEpicInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        title:
          type: string
          example: Shopping cart
        description:
          type: string
          example: Everything the buyer does before paying.
      required:
        - projectId
        - title
    CreateHandoffInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        fromAgent:
          type: string
          example: criativo
        toAgent:
          type: string
          example: po
        artifactId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRB
          description: ULID session_event артефакта, который стал причиной handoff.
        seAusente:
          type: boolean
          example: true
          description: >-
            Только если его ещё никто не получил (ADR 0182, RN-636): при
            ожидающем предложении цели в ЛЮБОЙ сессии проекта вернуть его
            (`desfecho: ja_oferecido`), а не заменять. Отправляет AppSec.
      required:
        - projectId
        - fromAgent
        - toAgent
    CreateModuleMapInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        modules:
          type: array
          items:
            type: object
            additionalProperties: true
          example:
            - name: api
              stack: NestJS
              responsibility: Rules and HTTP
              dependsOn:
                - db
              resources:
                cpus: 1
                memoryMb: 1024
                pidsLimit: 256
          description: >-
            Граф модулей. ЦИКЛ приводит к отклонению карты с 400 — проверка на
            уровне домена. Каждый модуль может объявить `resources` (cpus,
            memoryMb, pidsLimit — все три или ни одного); частичное объявление,
            значение выше потолка контейнера или СУММА по модулям выше него —
            тоже 400 (RN-683).
      required:
        - projectId
        - modules
    CreateProjectDto:
      type: object
      properties:
        name:
          type: string
          example: Checkout
          minLength: 2
        slug:
          type: string
          example: checkout
          pattern: ^[a-z0-9]+(-[a-z0-9]+)*$
          description: kebab-case. Уникален в пределах рабочего пространства.
        maxConsecutiveBlocked:
          type: number
          example: 3
          description: >-
            Предохранитель (Фаза 12b — RN-047): сколько последовательных задач,
            закончившихся блокировкой, останавливают dev agent модуля в
            idle_tripped. Если опущено, используется значение домена по
            умолчанию (DEFAULT_MAX_CONSECUTIVE_BLOCKED). Применяется со
            СЛЕДУЮЩЕЙ активации выполнения — не затрагивает уже работающих
            агентов.
        storyPromotion:
          type: string
          enum:
            - manual
            - auto
          example: auto
          description: >-
            Кто продвигает историю из `draft` в `ready` (Фаза 12c — RN-048).
            `auto` (по умолчанию для новых проектов начиная с RN-659): полная
            история сразу рождается `ready`, без шага человека. `manual`: PO
            предлагает, а ВЫ решаете во вкладке Backlog. Смена значения по
            умолчанию не переписала ни одного существующего проекта. Проверки
            домена (DoD/DoR/RF/правило/модули) ОДИНАКОВЫ в обоих режимах; режим
            меняет только то, кто запускает.
        executionMode:
          type: string
          enum:
            - container
            - mounted
            - runner
          example: container
          description: >-
            ГДЕ ВЫПОЛНЯЕТСЯ команда этого проекта (RN-169/RN-421 — ADR
            0072/0104). `container` (по умолчанию): папка, управляемая продуктом
            внутри PROJECT_WORKSPACES_ROOT, обычное поведение. `mounted`: ВАША
            папка, указанная в `workspacePath`, внутри общей для установки базы
            `BRABO_PROJECTS_BASE` (ADR 0141), — при создании проверяется только
            ФОРМАТ пути плюс эта база, а сама папка создаётся позже, когда Infra
            запускает контейнер (RN-501, ADR 0142). `runner`: ВАША папка,
            которой НЕ нужен bind-mount, — при создании проверяется только
            формат пути, и проект рождается «непроверенным»; запустите
            `brabo-runner --project <id> --dir <folder>` на своей машине, чтобы
            подтвердить его (RN-423).
        workspacePath:
          type: string
          example: /home/you/projects/store
          description: >-
            АБСОЛЮТНЫЙ путь к папке, обязательный, когда `executionMode` равен
            `mounted` или `runner`, и отклоняемый, когда он `container`. В ОБОИХ
            сейчас проверяется только ФОРМАТ — абсолютный, без `..`, не
            системный корень, не системная папка, не пересекается с checkout
            Brabo (RN-422/RN-423). `mounted` добавляет одно правило: он должен
            находиться внутри `BRABO_PROJECTS_BASE`, единственной папки вашей
            машины, которую видят контейнеры Brabo (ADR 0141), — без настроенной
            базы режим недоступен в этой установке, и создание об этом сообщает.
            Диска касаются позже: runner в `runner` и Infra при запуске
            контейнера в `mounted` (RN-501, ADR 0142).
        language:
          type: string
          example: pt-BR
          maxLength: 35
          description: >-
            Язык ПРОЕКТА (RN-619) — его используют общие артефакты и ходы без
            автора-человека. Любой код BCP-47, распознаваемый сервером, хранимый
            в каноническом виде; нераспознанный — это 400. Если опущен, проект
            начинает с эффективного языка ответов своего создателя (RN-618:
            выбор в учётной записи > подтверждённое обнаружение > язык
            интерфейса).
      required:
        - name
        - slug
    CreateSessionDto:
      type: object
      properties:
        kind:
          type: string
          enum:
            - consultiva
            - criativa
          example: criativa
          description: >-
            Намерение сессии. `consultiva` (консультационная) — только беседа;
            `criativa` (творческая) — та, что производит: она открывает
            генерацию идей с агентом Креатив и единственная входит в выполнение.
            ОБЯЗАТЕЛЬНО: без него ответ — 400, потому что тип — выбор того, кто
            открывает, и он записывается.
        name:
          type: string
          example: Cart checkout
          maxLength: 80
          description: >-
            Понятное имя, необязательно. Не заменяет хештег id — экраны
            составляют их вместе. Пустое считается отсутствующим.
      required:
        - kind
    CreateSocketTicketDto:
      type: object
      properties:
        scope:
          type: string
          enum:
            - heartbeat
            - terminal
          example: heartbeat
          description: >-
            `heartbeat` требует роли `viewer`; `terminal` требует `developer` —
            ту же минимальную роль, что и `MIN_ROLE_FOR_ACTION_TYPE.terminal`.
      required:
        - scope
    CreateStoryInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        epicId:
          type: string
          format: uuid
          example: 01JC4Z0000EPICO000000000001
        title:
          type: string
          example: Add item to cart
        description:
          type: string
          example: As a buyer, I want to gather items before paying.
        rf:
          example:
            - The cart accepts up to 50 items
          type: array
          items:
            type: string
        rnf:
          example:
            - Response under 200ms at p95
          type: array
          items:
            type: string
        dod:
          example:
            - Unit tests green
          type: array
          items:
            type: string
        dor:
          example:
            - Module defined
          type: array
          items:
            type: string
        businessRuleIds:
          example:
            - RN-014
          description: >-
            Каждый id должен ссылаться на СУЩЕСТВУЮЩЕЕ событие
            `artifact.business_rule` — выдуманный id отклоняется.
          type: array
          items:
            type: string
      required:
        - projectId
        - epicId
        - title
    CreateTaskInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        storyId:
          type: string
          format: uuid
          example: 01JC4Z0000HISTORIA000000001
        title:
          type: string
          example: Endpoint POST /cart/items
        description:
          type: string
          example: Accepts SKU and quantity; validates stock.
      required:
        - projectId
        - storyId
        - title
    CreateWorkspaceDto:
      type: object
      properties:
        name:
          type: string
          example: Acme Corp
          minLength: 2
        slug:
          type: string
          example: acme-corp
          pattern: ^[a-z0-9]+(-[a-z0-9]+)*$
          description: kebab-case. Уникален в системе и виден в URL.
      required:
        - name
        - slug
    CredentialedProvidersResponseDto:
      type: object
      properties:
        providers:
          type: array
          example:
            - anthropic
            - openrouter
          description: >-
            LLM providers the workspace owner of record has a key for. Names
            only; `ollama` never appears.
          items:
            type: string
            enum:
              - ollama
              - anthropic
              - openai
              - openrouter
              - nvidia-nim
              - together
              - deepinfra
              - bitdeer
              - vultr
              - 9router
              - opencode-zen
      required:
        - providers
    CredentialSpendPorMesResponseDto:
      type: object
      properties:
        mes:
          type: string
          example: '2026-08-01T00:00:00.000Z'
          format: date-time
        costMicros:
          type: number
          example: 1250000
        chamadas:
          type: number
          example: 42
      required:
        - mes
        - costMicros
        - chamadas
    CredentialSpendPorProviderResponseDto:
      type: object
      properties:
        provider:
          type: string
          example: openrouter
        temCredencial:
          type: boolean
          example: true
          description: >-
            Учётные данные существуют СЕГОДНЯ. `false` — исторические расходы по
            ключу, который уже удалён, — потребление произошло и не исчезает из
            отчёта из-за этого.
        costMicros:
          type: number
          example: 1250000
        inputTokens:
          type: number
          example: 120000
        outputTokens:
          type: number
          example: 35000
        chamadas:
          type: number
          example: 42
        costMicrosAgentes:
          type: number
          example: 900000
          description: >-
            Часть, потраченная АГЕНТАМИ, — та статья, которую RN-058 начала
            относить на владельца.
        costMicrosPessoas:
          type: number
          example: 350000
          description: >-
            Часть, потраченная ЛЮДЬМИ в чате. Идёт из того же ключа, и это
            другой вопрос.
        porMes:
          type: array
          items:
            $ref: '#/components/schemas/CredentialSpendPorMesResponseDto'
      required:
        - provider
        - temCredencial
        - costMicros
        - inputTokens
        - outputTokens
        - chamadas
        - costMicrosAgentes
        - costMicrosPessoas
        - porMes
    CredentialSpendResponseDto:
      type: object
      properties:
        workspaceId:
          type: string
          example: 9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b
          format: uuid
        ownerId:
          type: string
          example: 9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b
          format: uuid
          description: >-
            Владелец ключей — тот, кто финансирует агентов этого рабочего
            пространства (RN-058).
        meses:
          type: number
          example: 6
        totalMicros:
          type: number
          example: 1250000
        porProvider:
          type: array
          items:
            $ref: '#/components/schemas/CredentialSpendPorProviderResponseDto'
      required:
        - workspaceId
        - ownerId
        - meses
        - totalMicros
        - porProvider
    CredentialTestResultResponseDto:
      type: object
      properties:
        resultado:
          type: string
          enum:
            - ok
            - recusado
            - nao_suportado
          example: recusado
          description: >-
            `ok` — провайдер принял учётные данные. `recusado` — провайдер их
            отклонил (недействительный/отозванный ключ, нет баланса, сеть).
            `nao_suportado` — у этого провайдера нет проверенного endpoint для
            проверки (`ollama`, `anthropic`, `openai`): НИЧЕГО не проверено, и
            сказать здесь «ok» было бы ложью.
        motivo:
          type: string
          example: 'connection test failed for openrouter: openrouter responded 401'
          description: Только при `recusado` — причина, которую назвал провайдер.
      required:
        - resultado
    CriterioDoAceiteResponseDto:
      type: object
      properties:
        regras:
          type: number
          example: 4
          description: Бизнес-правила в проекте.
        cobertas:
          type: number
          example: 4
          description: >-
            Правила, на которые ссылается хотя бы одна история, — равно
            `regras`.
        repositorio:
          type: string
          enum:
            - local
            - a_provisionar_local
          example: a_provisionar_local
          description: >-
            `local`: у проекта уже был репозиторий `local`.
            `a_provisionar_local`: пока нет — принятие создаёт репозиторий
            `local`.
      required:
        - regras
        - cobertas
        - repositorio
    DecideBootstrapPlanDto:
      type: object
      properties:
        planGeneratedAt:
          type: string
          example: '2026-08-01T23:45:00.000Z'
          format: date-time
          description: >-
            `generatedAt` плана, который вы ВИДЕЛИ. Оптимистическая блокировка:
            если план с тех пор пересоздан (повторное подключение или изменение
            репозитория), решение отклоняется с 409, а не применяет «да», данное
            о чём-то другом.
      required:
        - planGeneratedAt
    DecideBootstrapPlanResponseDto:
      type: object
      properties:
        repository:
          $ref: '#/components/schemas/ProvisionedRepositoryResponseDto'
        bootstrap:
          description: >-
            Где оказался bootstrap. При `skip` курсор НЕ продвигается: ни один
            шаг не выполнялся, и запись об этом говорит.
          allOf:
            - $ref: '#/components/schemas/PassoDeBootstrapResponseDto'
      required:
        - repository
        - bootstrap
    DecideProjectImageInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
        image:
          type: string
          example: node:22-bookworm-slim
          description: >-
            Ссылка OCI с явным ТЕГОМ или digest. `latest` ОТКЛОНЯЕТСЯ с 400 —
            правило на уровне домена.
        rationale:
          type: string
          example: >-
            The module_map is TypeScript on Node 22; the slim variant has the
            runtime and nothing else.
          description: Почему этот образ. Минимум 10 символов.
        network:
          type: string
          enum:
            - none
            - egress
          default: none
          description: >-
            Сетевая позиция контейнера. `none` — значение по умолчанию, и именно
            оно делает фразу «внутри агент свободен» безопасной.
        resources:
          type: object
          additionalProperties: true
          example:
            cpus: 2
            memoryMb: 4096
            pidsLimit: 512
          description: >-
            Лимит ресурсов. Выше максимума решение ОТКЛОНЯЕТСЯ, а не молча
            понижается.
      required:
        - projectId
        - image
        - rationale
    DecisaoDeImagemResponseDto:
      type: object
      properties:
        image:
          type: string
          example: node:22-bookworm-slim
          description: >-
            Ссылка OCI с явным ТЕГОМ или digest. `latest` отклоняется: артефакт
            должен говорить то же самое и через шесть месяцев.
        rationale:
          type: string
          example: >-
            The module_map is all TypeScript over Node; the slim variant is
            enough and reduces surface.
          description: Почему ЭТОТ образ. Именно это делает решение проверяемым.
        network:
          type: string
          enum:
            - none
            - egress
          example: none
          description: >-
            Сетевая позиция контейнера, решаемая ОДИН раз в артефакте, а не от
            команды к команде. `egress` — это стоимость и поверхность атаки:
            Архитектор просит, пользователь разрешает при создании.
        resources:
          $ref: '#/components/schemas/RecursosDoContainerResponseDto'
      required:
        - image
        - rationale
        - network
        - resources
    DeclararRedeDto:
      type: object
      properties:
        ports:
          description: >-
            Up to 10 TCP ports published on 127.0.0.1 only. `host` 1024-65535,
            never an installation port, no duplicates.
          type: array
          items:
            $ref: '#/components/schemas/PortaDto'
        dns:
          description: Up to 3 literal IPs.
          type: array
          items:
            type: string
        extraHosts:
          description: >-
            Up to 10 `--add-host` entries: name in [a-z0-9.-], never localhost
            or an installation service, IPv4 literal.
          type: array
          items:
            $ref: '#/components/schemas/HostExtraDto'
    DeclareModuleContractsInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
        contratos:
          description: >-
            ВЕСЬ список контрактов: каждый вызов — новая версия, заменяющая
            предыдущую. То, что модуль ПОТРЕБЛЯЕТ, здесь не указано — оно
            выводится из `dependsOn` текущего module_map при чтении.
          type: array
          items:
            $ref: '#/components/schemas/ContratoDeModuloInternalDto'
      required:
        - projectId
        - contratos
    DelegationResponseDto:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: 01JC4Z0000DELEGACAO00000001
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        sessionId:
          type: string
          format: uuid
          example: 01JC4Z0000SESSAO000000000001
        taskId:
          type: object
          format: uuid
          nullable: true
          example: 01JC4Z0000TAREFA00000000001
          description: null, когда за областью нет задачи backlog (Infra, Фаза 8c).
        area:
          type: string
          example: qa
        leadAgent:
          type: string
          example: qa-lead
        subagent:
          type: string
          example: qa-automacao
        status:
          type: string
          enum:
            - completed
            - failed
            - dispensed
          example: completed
        parecerArtifactId:
          type: object
          nullable: true
          example: evt_01jc4z0000parecer000000001
        failureOrigin:
          type: string
          enum:
            - infra
            - modelo
            - codigo
            - politica
          nullable: true
          example: null
        failureReason:
          type: object
          nullable: true
          example: null
        justification:
          type: object
          nullable: true
          example: null
        createdAt:
          type: string
          example: '2026-07-30T12:00:00.000Z'
      required:
        - id
        - projectId
        - sessionId
        - taskId
        - area
        - leadAgent
        - subagent
        - status
        - parecerArtifactId
        - failureOrigin
        - failureReason
        - justification
        - createdAt
    DenyActionDto:
      type: object
      properties:
        reason:
          type: string
          example: The command would delete the working directory.
          description: >-
            Остаётся в журнале событий вместе с отклонением. Необязательно, но
            рекомендуется.
    DevContextAdrResponseDto:
      type: object
      properties:
        title:
          type: string
          example: 0007 — Postgres queue instead of Redis
        content:
          type: string
          example: |-
            ## Context

            …
        securityRelevant:
          type: boolean
          example: false
          description: >-
            Отмечено Архитектором. Становится информационным чек-листом в
            вердикте SecOps — построчной корреляции нет, и утверждать обратное
            значило бы исказить глубину проверки.
      required:
        - title
        - content
        - securityRelevant
    DevContextBusinessRuleResponseDto:
      type: object
      properties:
        title:
          type: string
          example: Cart accepts at most 50 items
        description:
          type: string
          example: Above that, the addition is refused with 409.
      required:
        - title
        - description
    DevContextDecisaoResponseDto:
      type: object
      properties:
        id:
          type: string
        questionId:
          type: string
          nullable: true
        pergunta:
          type: string
          description: Вопрос, по которому принято решение.
        decisao:
          type: string
          description: Решение пользователя.
        decididaEm:
          type: string
          format: date-time
      required:
        - id
        - questionId
        - pergunta
        - decisao
        - decididaEm
    DevContextModuleOpenTaskResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01J0000000000000000000000
        title:
          type: string
          example: Expose POST /links
        status:
          type: string
          example: todo
        storyTitle:
          type: string
          example: Members create short links
      required:
        - id
        - title
        - status
        - storyTitle
    DevContextSiblingTaskResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01J0000000000000000000000
        title:
          type: string
          example: Expose POST /login returning a JWT
        status:
          type: string
          example: todo
      required:
        - id
        - title
        - status
    DevTaskContextResponseDto:
      type: object
      properties:
        task:
          $ref: '#/components/schemas/TaskResponseDto'
        story:
          $ref: '#/components/schemas/StoryResponseDto'
        businessRules:
          type: array
          items:
            $ref: '#/components/schemas/DevContextBusinessRuleResponseDto'
        adrs:
          description: >-
            Применимые ADR: сквозные (без объявленного модуля) плюс те, что
            ссылаются на модуль этого агента.
          type: array
          items:
            $ref: '#/components/schemas/DevContextAdrResponseDto'
        siblingTasks:
          description: >-
            Остальные задачи той же истории, с их статусом. Gate QA оценивает
            поставку по тому, что относится к ЭТОЙ задаче; требование соседней
            задачи — это замечание, никогда не отклонение (RN-765).
          type: array
          items:
            $ref: '#/components/schemas/DevContextSiblingTaskResponseDto'
        moduleOpenTasks:
          description: >-
            Задачи ДРУГИХ историй того же модуля, ещё не завершённые, с
            заголовком истории (не более 20). Требование, зависящее от того, что
            поставит другая история, — это замечание для gate QA, никогда не
            отклонение (RN-785).
          type: array
          items:
            $ref: '#/components/schemas/DevContextModuleOpenTaskResponseDto'
        moduleOpenTasksTotal:
          type: number
          description: >-
            Реальное число открытых задач модуля в других историях — больше, чем
            `moduleOpenTasks.length`, когда список обрезан.
        moduleContract:
          nullable: true
          description: >-
            Текущий контракт модуля задачи (`artifact.module_contracts`) —
            источник ИНТЕРФЕЙСА для gate QA. Когда история и контракт
            расходятся, вердикт называет расхождение, а не отклоняет dev,
            следовавшего контракту (RN-786). `null` без разрешимого модуля или
            контракта.
          type: object
          allOf:
            - $ref: '#/components/schemas/ContratoDeModuloResponseDto'
        primeiraDoModulo:
          type: boolean
          description: >-
            Является ли это ПЕРВОЙ задачей модуля: самый старый
            `backlog.task_claimed` проекта для `module` — это эта задача. Engine
            даёт ей двойной бюджет на задачу (RN-774). `false` без `module`.
        decisoes:
          description: >-
            RN-859: решения пользователя, привязанные к этой задаче, её истории
            или её правилам через `afeta`, — при конфликте они побеждают RF/DoR.
            Сначала новые, не более 10, каждый текст обрезан до 1 500 символов.
          type: array
          items:
            $ref: '#/components/schemas/DevContextDecisaoResponseDto'
        decisoesDoProjeto:
          description: >-
            RN-872 (ADR 0215): ПРОЧИЕ решения проекта — те, чей `afeta` не
            касается этой задачи. Отдельный раздел, в пределах ТОГО ЖЕ лимита в
            10, что и `decisoes` (решения задачи занимают лимит первыми),
            сначала новые.
          type: array
          items:
            $ref: '#/components/schemas/DevContextDecisaoResponseDto'
      required:
        - task
        - story
        - businessRules
        - adrs
        - siblingTasks
        - moduleOpenTasks
        - moduleOpenTasksTotal
        - moduleContract
        - primeiraDoModulo
        - decisoes
        - decisoesDoProjeto
    DiffLineResponseDto:
      type: object
      properties:
        kind:
          type: string
          enum:
            - add
            - del
            - ctx
          example: add
        content:
          type: string
          example: Always run the suite before opening the PR.
        lineNo:
          type: number
          example: 42
      required:
        - kind
        - content
    EpicComHistoriasResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000EPICO000000000001
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        sessionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
        title:
          type: string
          example: Shopping cart
        description:
          type: string
          example: Everything the buyer does before paying.
        createdAt:
          type: string
          example: '2026-07-25T08:30:00.000Z'
          format: date-time
        updatedAt:
          type: string
          example: '2026-07-25T08:30:00.000Z'
          format: date-time
        stories:
          type: array
          items:
            $ref: '#/components/schemas/StoryComTarefasResponseDto'
      required:
        - id
        - projectId
        - sessionId
        - title
        - description
        - createdAt
        - updatedAt
        - stories
    EpicResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000EPICO000000000001
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        sessionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
        title:
          type: string
          example: Shopping cart
        description:
          type: string
          example: Everything the buyer does before paying.
        createdAt:
          type: string
          example: '2026-07-25T08:30:00.000Z'
          format: date-time
        updatedAt:
          type: string
          example: '2026-07-25T08:30:00.000Z'
          format: date-time
      required:
        - id
        - projectId
        - sessionId
        - title
        - description
        - createdAt
        - updatedAt
    EstadoDoC4DiagramaResponseDto:
      type: object
      properties:
        status:
          type: string
          enum:
            - sem_diagrama
            - gerado
          example: sem_diagrama
          description: >-
            `sem_diagrama` — начальное состояние: Архитектор ещё не сгенерировал
            диаграмму.
        diagrama:
          nullable: true
          description: Текущая диаграмма или `null`, пока её нет.
          type: object
          allOf:
            - $ref: '#/components/schemas/C4DiagramaResponseDto'
        version:
          type: number
          example: 0
          description: >-
            Версия текущего артефакта; 0, когда диаграммы нет. Пересмотр
            означает повторную генерацию — история никогда не переписывается.
        eventId:
          type: object
          nullable: true
          example: 01JC4Z0000EVENTO000000002
        createdAt:
          type: object
          nullable: true
          format: date-time
      required:
        - status
        - diagrama
        - version
        - eventId
        - createdAt
    EstadoDoContainerResponseDto:
      type: object
      properties:
        status:
          type: string
          enum:
            - sem_decisao
            - decidido
          example: sem_decisao
          description: >-
            `sem_decisao` — начальное состояние каждого проекта, и именно оно
            закрывает gate RN-105: нет образа — нет контейнера, и вкладка Code
            не открывается.
        decisao:
          nullable: true
          description: Текущее решение или `null`, когда его ещё нет.
          type: object
          allOf:
            - $ref: '#/components/schemas/DecisaoDeImagemResponseDto'
        version:
          type: number
          example: 0
          description: >-
            Версия текущего артефакта; 0, когда решения нет. Пересмотр означает
            выпуск новой версии — история никогда не переписывается.
        eventId:
          type: object
          nullable: true
          example: 01JC4Z0000EVENTO000000001
          description: Id события, зафиксировавшего текущее решение, для аудита.
        decidedAt:
          type: object
          nullable: true
          format: date-time
      required:
        - status
        - decisao
        - version
        - eventId
        - decidedAt
    EstadoDoRoteamentoResponseDto:
      type: object
      properties:
        status:
          type: string
          enum:
            - sem_roteamento
            - roteado
          example: roteado
        roteamento:
          type: array
          items:
            $ref: '#/components/schemas/RoteamentoDeModuloResponseDto'
        version:
          type: number
          example: 1
        eventId:
          type: object
          example: 01JC4Z0000EVENTOROTEAMENTO01
          nullable: true
        createdAt:
          type: object
          example: '2026-08-30T12:00:00.000Z'
          nullable: true
      required:
        - status
        - roteamento
        - version
        - eventId
        - createdAt
    ExecucaoAtivadaResponseDto:
      type: object
      properties:
        sessionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
          description: Созданная сессия выполнения.
        modules:
          example:
            - api
            - web
          description: >-
            Модули `module_map`, получившие dev agent. Один агент на модуль,
            каждый в изолированном worktree.
          type: array
          items:
            type: string
      required:
        - sessionId
        - modules
    FirstAccountInternalDto:
      type: object
      properties:
        email:
          type: string
          example: voce@exemplo.dev
          description: >-
            Email владельца, введённый в TTY. Нормализуется до любой записи;
            именно его установщик записывает в свой маркер, поскольку email
            идентифицирует и не является секретом.
        senha:
          type: string
          example: uma frase longa e minha
          description: >-
            Читается без эха и подтверждается установщиком. Используется и
            отбрасывается — никогда не записывается в `.env`, маркер или лог и
            никогда не генерируется никаким кодом: нет пути, при котором учётная
            запись рождается с паролем, выбранным скриптом. Минимум — политика
            ДОМЕНА, тот же код, что использует регистрация; максимальная длина
            здесь — защита argon2id, а не политика.
        nome:
          type: string
          example: Fulana de Tal
          description: >-
            Отображаемое имя. Если опущено: имя личного рабочего пространства
            берётся из локальной части email (RN-410) — ничего не выдумывается.
      required:
        - email
        - senha
    FirstAccountInternalResponseDto:
      type: object
      properties:
        userId:
          type: string
          format: uuid
          example: 01JC4Z0000USUARIO0000000001
          description: Созданный пользователь.
        email:
          type: string
          example: voce@exemplo.dev
          description: >-
            НОРМАЛИЗОВАННЫЙ email — то, что реально записано, может отличаться
            от введённого. Именно его установщик записывает в свой маркер.
        workspaceId:
          type: string
          format: uuid
          example: 01JC4Z0000WORKSPACE00000001
          description: >-
            Личное рабочее пространство, рождённое в ТОЙ ЖЕ транзакции (RN-410),
            с учётной записью в роли `owner`. Без него установка завершилась бы
            рабочим входом и дашбордом, где «Новый проект» негде создать.
      required:
        - userId
        - email
        - workspaceId
    GateAbertoResponseDto:
      type: object
      properties:
        gateStatus:
          type: string
          enum:
            - awaiting_qa
            - awaiting_secops
            - awaiting_user
          example: awaiting_qa
      required:
        - gateStatus
    GateRegistryResponseDto:
      type: object
      properties:
        version:
          type: number
          example: 1
        gates:
          type: array
          items:
            $ref: '#/components/schemas/GateResponseDto'
      required:
        - version
        - gates
    GateResponseDto:
      type: object
      properties:
        id:
          type: string
          example: qa-verificada
        fluxo:
          type: string
          example: pr
        dono:
          type: string
          example: area-qa
        entrada:
          example:
            - pr-aberta
          type: array
          items:
            type: string
        entregavel:
          oneOf:
            - type: string
            - type: array
              items:
                type: string
          example: veredito-qa
        verificacao:
          type: string
          enum:
            - script
            - humana
        severidade:
          type: string
          enum:
            - block
            - warn
        aprovacao_humana:
          type: boolean
          description: >-
            Решение за пользователем — либо прямой клик, либо делегированное
            политикой, которую он написал. Инвариант для четырёх конституционно
            ручных gate (RN-071).
        status:
          type: string
          enum:
            - active
            - planned
        evidencia:
          type: object
          additionalProperties: true
          description: >-
            Где находится доказательство прохождения gate: `event_log` (типы +
            фильтр payload), `teste` или `ci` (целевой путь). Отсутствует у gate
            `planned`.
        backlog:
          type: string
          example: ADR 0053
      required:
        - id
        - fluxo
        - dono
        - entrada
        - entregavel
        - verificacao
        - severidade
        - aprovacao_humana
        - status
    GateResumoResponseDto:
      type: object
      properties:
        id:
          type: string
          example: qa-verificada
        fluxo:
          type: string
          example: pr
          description: Поток, к которому относится gate.
        dono:
          type: string
          example: area-qa
          description: Кто оценивает.
        entrada:
          example:
            - pr-aberta
          description: Что должно существовать, чтобы gate открылся.
          type: array
          items:
            type: string
        entregavel:
          type: object
          example: veredito-qa
          description: Что gate производит при прохождении.
        aprovacaoHumana:
          type: boolean
          example: true
          description: >-
            Решение за пользователем — прямое по клику ИЛИ делегированное
            политикой, которую он сам написал (см. RN-071).
        severidade:
          type: string
          example: block
          enum:
            - block
            - warn
      required:
        - id
        - fluxo
        - dono
        - entrada
        - entregavel
        - aprovacaoHumana
        - severidade
    GatesResponseDto:
      type: object
      properties:
        version:
          type: number
          example: 1
          description: Версия формата реестра.
        gates:
          description: >-
            Только АКТИВНЫЕ gate. Gate `planned` описывает будущую роль и не
            должен появляться на экране, который говорит, что происходит прямо
            сейчас.
          type: array
          items:
            $ref: '#/components/schemas/GateResumoResponseDto'
      required:
        - version
        - gates
    GateVerdictResponseDto:
      type: object
      properties:
        nextAction:
          type: string
          enum:
            - correct
            - run_secops
            - done
            - blocked
            - await_user
          example: run_secops
          description: >-
            Что происходит дальше. `correct` возвращает к dev; `run_secops`
            продвигает gate; `done` передаёт пользователю; `await_user` ставит
            цикл на паузу до ответа на открытый вопрос (RN-860); `blocked`
            означает, что лимит исправлений исчерпан и разблокировать может
            только человек.
        task:
          $ref: '#/components/schemas/TaskResponseDto'
        openQuestionId:
          type: string
          format: uuid
          description: 'При `await_user`: открытый вопрос, которого ждёт цикл.'
      required:
        - nextAction
        - task
    GitAuthorizeUrlResponseDto:
      type: object
      properties:
        authorizeUrl:
          type: string
          example: >-
            https://github.com/login/oauth/authorize?client_id=…&state=…&redirect_uri=…
          description: >-
            Куда отправить браузер. `state` подписан HMAC с
            `GIT_OAUTH_STATE_SECRET` — именно это не даёт подделать callback.
      required:
        - authorizeUrl
    HandoffResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000HANDOFF00000000001
        sessionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        fromAgent:
          type: string
          example: criativo
          description: Slug агента, который передал эстафету.
        toAgent:
          type: string
          example: po
          description: Slug принимающего агента.
        artifactId:
          type: object
          example: 01JC4Z0000ARTEFATO000000001
          nullable: true
          description: Артефакт, ставший причиной handoff (product_brief, module_map…).
        status:
          type: string
          enum:
            - offered
            - accepted
            - completed
            - rejected
            - superseded
          example: offered
          description: >-
            Это поле ИЗМЕНЯЕМО — это текущее состояние. Каждый переход также
            становится неизменяемым событием `handoff.*` в журнале, где и живёт
            история. `superseded` (ADR 0182, RN-635): предложение перестало быть
            текущим — целевой агент был активирован другим путём, или более
            новое предложение той же цели в проекте заменило его
            (`handoff.superseded`). Принять можно только `offered`.
        createdAt:
          type: string
          example: '2026-07-24T10:00:00.000Z'
          format: date-time
        updatedAt:
          type: string
          example: '2026-07-24T10:05:00.000Z'
          format: date-time
      required:
        - id
        - sessionId
        - projectId
        - fromAgent
        - toAgent
        - artifactId
        - status
        - createdAt
        - updatedAt
    HealthStatusResponseDto:
      type: object
      properties:
        service:
          type: string
          enum:
            - api
            - engine
          example: api
        status:
          type: string
          enum:
            - ok
            - error
          example: ok
        timestamp:
          type: string
          example: '2026-07-27T15:40:00.000Z'
          format: date-time
        details:
          type: object
          additionalProperties: true
          description: >-
            Только при `status=error`, с сообщением о сбое. У здорового ответа
            деталей нет вовсе, намеренно: liveness — не место для раскрытия
            топологии.
      required:
        - service
        - status
        - timestamp
    HostExtraDto:
      type: object
      properties:
        name:
          type: string
          example: api.local
        ip:
          type: string
          example: 10.0.0.5
      required:
        - name
        - ip
    HuggingFaceModelResponseDto:
      type: object
      properties:
        repoId:
          type: string
          example: meta-llama/Llama-3.1-8B-Instruct-GGUF
          description: '`<publisher>/<modelo>` в Hugging Face Hub.'
        publisher:
          type: string
          example: meta-llama
        downloads:
          type: number
          example: 182034
        likes:
          type: number
          example: 210
        official:
          type: boolean
          example: true
          description: >-
            Издатель находится в курируемом списке разрешённых известных
            производителей (`domain/llm/huggingface-official-publishers.ts`).
            `false` — любая повторная загрузка третьей стороной; экран
            показывает предупреждение о безопасности, когда
            `includeCommunity=true` вернул этот результат.
      required:
        - repoId
        - publisher
        - downloads
        - likes
        - official
    HybridSearchHitResponseDto:
      type: object
      properties:
        chunkId:
          type: string
        scope:
          type: string
          enum:
            - docs
            - adr
            - session
            - local
        content:
          type: string
        score:
          type: number
          description: >-
            Комбинированное (RAG_SEARCH_WEIGHT_VECTOR*vector +
            RAG_SEARCH_WEIGHT_LEXICAL*lexical), уже отфильтрованное порогом, —
            каждый возвращённый результат его прошёл.
        vectorScore:
          type: number
          nullable: true
          description: >-
            Косинусное сходство, 0..1. `null`, когда у этого фрагмента не было
            вектора.
        lexicalScore:
          type: number
          nullable: true
          description: >-
            Нормализованный `ts_rank`, 0..1. `null`, когда запрос не совпал в
            этом фрагменте.
        origin:
          type: object
          additionalProperties: true
          description: >-
            Дискриминированное объединение по `kind`: "file"
            (`sourcePath`/`headingPath`) или "session" (`sessionId`/`eventId`).
            См. `domain/rag/rag-citation.ts`.
      required:
        - chunkId
        - scope
        - content
        - score
        - vectorScore
        - lexicalScore
        - origin
    HybridSearchRequestDto:
      type: object
      properties:
        query:
          type: string
          example: how does the PR gate work
          description: >-
            От 2 до 500 символов — то же правило, что и у поиска во вкладке
            Code.
        scopes:
          type: array
          description: Если отсутствует, поиск идёт по всем четырём областям (RN-219/454).
          items:
            type: string
            enum:
              - docs
              - adr
              - session
              - local
        limit:
          type: number
          example: 10
          description: От 1 до 10 — если отсутствует, используется лимит.
      required:
        - query
    HybridSearchResponseDto:
      type: object
      properties:
        query:
          type: string
        searchId:
          type: string
          nullable: true
          format: uuid
          description: >-
            Строка `rag_searches`, оставленная этим поиском (RN-479), — id, к
            которому привязывается голос. `null` означает, что строка телеметрии
            НЕ записана (вставка не удалась), что не то же самое, что «нет
            результатов»: привязать голос не к чему, и интерфейсу нужно их
            различать, чтобы не предлагать элемент управления, который api
            отклонит.
        hits:
          type: array
          items:
            $ref: '#/components/schemas/HybridSearchHitResponseDto'
        vectorAvailable:
          type: boolean
          description: >-
            `false`, когда провайдер эмбеддингов не ответил, — поиск выполнился
            только с лексическим сигналом (RN-233).
        vectorUnavailableReason:
          type: string
      required:
        - query
        - searchId
        - hits
        - vectorAvailable
    HypothesisDraftDto:
      type: object
      properties:
        agenteAlvo:
          type: string
          example: dev-api
        observacao:
          type: string
          example: The agent reopened the same task three times.
        hipotese:
          type: string
          example: The instructions don't say when the task is done.
        sugestao:
          type: string
          example: Add an explicit definition-of-done criterion.
        confiancaPercent:
          type: number
          example: 72
          minimum: 0
          maximum: 100
          description: Сообщено самой моделью, не измерено.
        evidenceEventIds:
          example:
            - 01JC4Z8QK3M7YV2N5T9B0PXHRB
          description: >-
            Как минимум одно, и каждое должно СУЩЕСТВОВАТЬ в этой сессии.
            Выдуманные доказательства отклоняются с 400 — именно это отличает
            гипотезу от мнения.
          type: array
          items:
            type: string
        terminationAnalysis:
          description: ОБЯЗАТЕЛЬНО, когда `cause` указывает на аварийное завершение.
          allOf:
            - $ref: '#/components/schemas/HypothesisTerminationAnalysisDto'
      required:
        - agenteAlvo
        - observacao
        - hipotese
        - sugestao
        - confiancaPercent
        - evidenceEventIds
    HypothesisResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000HIPOTESE000000001
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        sessionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
        analysisId:
          type: string
          example: 01JC4Z0000ANALISE0000000001
          description: Раунд анализа, породивший эту гипотезу.
        agenteAlvo:
          type: string
          example: dev-api
          description: О каком агенте гипотеза.
        observacao:
          type: string
          example: The agent reopened the same task three times.
        hipotese:
          type: string
          example: The instructions don't say when to consider the task done.
        sugestao:
          type: string
          example: Add an explicit definition-of-done criterion to the instruction.
        confiancaPercent:
          type: number
          example: 72
          minimum: 0
          maximum: 100
          description: Уверенность, заявленная самой моделью. Не измерение, а самоотчёт.
        evidenceEventIds:
          example:
            - 01JC4Z8QK3M7YV2N5T9B0PXHRB
          description: >-
            События журнала, подтверждающие гипотезу. Именно это делает
            доказательства проверяемыми, а не принимаемыми на веру.
          type: array
          items:
            type: string
        terminationAnalysis:
          nullable: true
          description: Присутствует только в гипотезах об аварийно завершённой сессии.
          type: object
          allOf:
            - $ref: '#/components/schemas/AnaliseDeTerminoResponseDto'
        status:
          type: string
          enum:
            - proposed
            - accepted
            - dismissed
          example: proposed
        decidedBy:
          type: object
          example: null
          nullable: true
        decidedAt:
          type: object
          example: null
          format: date-time
          nullable: true
        createdAt:
          type: string
          example: '2026-07-26T18:00:00.000Z'
          format: date-time
        updatedAt:
          type: string
          example: '2026-07-26T18:00:00.000Z'
          format: date-time
      required:
        - id
        - projectId
        - sessionId
        - analysisId
        - agenteAlvo
        - observacao
        - hipotese
        - sugestao
        - confiancaPercent
        - evidenceEventIds
        - terminationAnalysis
        - status
        - decidedBy
        - decidedAt
        - createdAt
        - updatedAt
    HypothesisTerminationAnalysisDto:
      type: object
      properties:
        causa:
          type: string
          example: heartbeat_timeout
          description: >-
            ИСТОЧНИК сбоя. Никогда не выводится методом исключения — урок из ADR
            0020.
        estadoDaSessao:
          type: string
          example: closed_abnormally
        analise:
          type: string
          example: The agent stopped responding after a 90s tool call.
      required:
        - causa
        - estadoDaSessao
        - analise
    IdiomaEfetivoResponseDto:
      type: object
      properties:
        language:
          type: string
          example: pt-BR
        origin:
          type: string
          enum:
            - sessao
            - conta
            - detectado
            - interface
          example: interface
      required:
        - language
        - origin
    ImagemDecididaResponseDto:
      type: object
      properties:
        decisao:
          $ref: '#/components/schemas/DecisaoDeImagemResponseDto'
        version:
          type: number
          example: 1
        containerDePe:
          type: object
          nullable: true
          example:
            status: running
            imageVersion: 2
          description: >-
            The `running`/`stopped` container registered for the project, or
            null. The new decision does NOT change it: it only applies once the
            container is recreated (stop, remove, start).
      required:
        - decisao
        - version
        - containerDePe
    ImagemParaOBrokerResponseDto:
      type: object
      properties:
        image:
          type: string
          example: node:22-bookworm-slim
          description: Ссылка OCI с явным тегом или digest.
        network:
          type: string
          enum:
            - none
            - egress
          example: none
        resources:
          $ref: '#/components/schemas/RecursosDoContainerResponseDto'
      required:
        - image
        - network
        - resources
    ImportLlmCredentialDto:
      type: object
      properties:
        provider:
          type: string
          enum:
            - anthropic
            - openai
            - openrouter
            - nvidia-nim
            - together
            - deepinfra
            - bitdeer
            - vultr
            - 9router
            - opencode-zen
          example: anthropic
        apiKey:
          type: string
          maxLength: 512
          description: >-
            The API key in plain text. Encrypted before touching the database;
            NEVER logged and NEVER returned.
        origem:
          type: string
          example: 'codex: ~/.codex/auth.json'
          maxLength: 200
          description: Tool and path the key was read from. Audit log only; never a value.
        substituir:
          type: boolean
          description: Must be `true` to replace an existing credential of this provider.
      required:
        - provider
        - apiKey
        - origem
    ImportLlmCredentialResponseDto:
      type: object
      properties:
        provider:
          type: string
          enum:
            - anthropic
            - openai
            - openrouter
            - nvidia-nim
            - together
            - deepinfra
            - bitdeer
            - vultr
            - 9router
            - opencode-zen
        substituida:
          type: boolean
          description: Whether an existing credential was replaced.
      required:
        - provider
        - substituida
    IndexDocsReportResponseDto:
      type: object
      properties:
        filesScanned:
          type: number
        docsChunks:
          type: number
        adrChunks:
          type: number
        truncated:
          type: boolean
        embedding:
          $ref: '#/components/schemas/IndexEmbeddingResponseDto'
      required:
        - filesScanned
        - docsChunks
        - adrChunks
        - truncated
        - embedding
    IndexEmbeddingResponseDto:
      type: object
      properties:
        available:
          type: boolean
        embedded:
          type: number
        skipped:
          type: number
        reason:
          type: string
      required:
        - available
        - embedded
        - skipped
    InfraArtifactResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000INFRAARTEFATO00001
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        sessionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
        title:
          type: string
          example: api's production Dockerfile
        prActionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRD
          description: >-
            Id действия `open_infra_pr` (`proposed_action`), которое открыло PR.
            Это то, что engine знает в ответ, — отдельного id артефакта нет.
        gateStatus:
          type: string
          enum:
            - awaiting_qa
            - awaiting_secops
            - awaiting_user
          example: awaiting_qa
          description: Проходит ТЕ ЖЕ gate QA и SecOps, что и PR dev.
        gateCorrectionCount:
          type: number
          example: 0
        blocked:
          type: boolean
          example: false
        blockedReason:
          type: object
          example: null
          nullable: true
        createdAt:
          type: string
          example: '2026-07-26T09:00:00.000Z'
          format: date-time
        updatedAt:
          type: string
          example: '2026-07-26T09:20:00.000Z'
          format: date-time
      required:
        - id
        - projectId
        - sessionId
        - title
        - prActionId
        - gateStatus
        - gateCorrectionCount
        - blocked
        - blockedReason
        - createdAt
        - updatedAt
    InfraContextAdrResponseDto:
      type: object
      properties:
        title:
          type: string
          example: 0025 — Kubernetes deploy with Kustomize
        content:
          type: string
          example: |-
            ## Context

            …
      required:
        - title
        - content
    InfraContextResponseDto:
      type: object
      properties:
        moduleMap:
          nullable: true
          type: object
          allOf:
            - $ref: '#/components/schemas/ModuleMapResponseDto'
        adrs:
          type: array
          items:
            $ref: '#/components/schemas/InfraContextAdrResponseDto'
        gitProvider:
          type: string
          enum:
            - local
            - github
            - gitlab
          nullable: true
          example: github
          description: >-
            null, когда проект ещё не создал репозиторий. Субагент Workflows
            (Фаза 8c) решает по этому формат CI-конвейера: "gitlab" генерирует
            .gitlab-ci.yml, любое другое значение — GitHub Actions.
        moduleRouting:
          description: >-
            Кандидаты, маршрутизированные Архитектором (route_modules_to_infra,
            ADR 0131), — среди них Infra Lead выбирает при предложении
            container_start (ADR 0130/0133). "sem_roteamento", когда Архитектор
            ещё ничего не маршрутизировал.
          allOf:
            - $ref: '#/components/schemas/EstadoDoRoteamentoResponseDto'
        projectImage:
          nullable: true
          description: >-
            RN-852: ТЕКУЩЕЕ решение artifact.project_image (образ, сеть,
            ресурсы) — FROM в Dockerfile и сеть, которую соблюдает compose
            Infra. null, когда образ не выбран.
          type: object
          allOf:
            - $ref: '#/components/schemas/DecisaoDeImagemResponseDto'
      required:
        - moduleMap
        - adrs
        - gitProvider
        - moduleRouting
        - projectImage
    InfraGateVerdictResponseDto:
      type: object
      properties:
        nextAction:
          type: string
          enum:
            - correct
            - run_secops
            - done
            - blocked
          example: done
        artifact:
          $ref: '#/components/schemas/InfraArtifactResponseDto'
      required:
        - nextAction
        - artifact
    InfraPrFileResponseDto:
      type: object
      properties:
        path:
          type: string
          example: docker/api/Dockerfile.prod
        content:
          type: string
          example: |-
            FROM node:24.11.1-alpine3.21 AS build
            …
      required:
        - path
        - content
    InfraPrFilesResponseDto:
      type: object
      properties:
        title:
          type: string
          example: api's production Dockerfile
        files:
          description: >-
            Содержимое берётся из собственного payload `proposed_action`:
            инфраструктурный артефакт НИКОГДА не касается worktree, так же как
            ADR.
          type: array
          items:
            $ref: '#/components/schemas/InfraPrFileResponseDto'
      required:
        - title
        - files
    InstructionVersionResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000VERSAO00000000001
        version:
          type: number
          example: 4
        content:
          type: string
          example: |-
            # dev-api

            Always run the suite before opening the PR.
        createdBy:
          type: object
          example: null
          nullable: true
        sourceActionId:
          type: object
          example: null
          nullable: true
          description: '`proposed_action` патча, породивший эту версию, если он был.'
        sourceHypothesisId:
          type: object
          example: 01JC4Z0000HIPOTESE000000001
          nullable: true
          description: Гипотеза Психолога, ставшая причиной патча, — замкнутый цикл.
        note:
          type: object
          example: Explicit definition-of-done criterion
          nullable: true
        createdAt:
          type: string
          example: '2026-07-26T14:00:00.000Z'
          format: date-time
        isCurrent:
          type: boolean
          example: true
          description: Является ли это действующей версией.
        diff:
          description: >-
            Diff этой версии относительно предыдущей, вычисленный на сервере,
            чтобы интерфейсу не нужен был собственный инструмент сравнения.
            Самая старая сравнивается с пустой, поэтому отображается целиком как
            добавление.
          allOf:
            - $ref: '#/components/schemas/TextDiffResponseDto'
      required:
        - id
        - version
        - content
        - createdBy
        - sourceActionId
        - sourceHypothesisId
        - note
        - createdAt
        - isCurrent
        - diff
    IssuePatRequestDto:
      type: object
      properties:
        name:
          type: string
          example: laptop
          description: Имя, чтобы вы потом узнали этот токен, — не уникально.
        expiresInDays:
          type: number
          example: 90
          description: >-
            Истекает через N дней. Опущено = без срока действия (можно отозвать
            в любой момент, но сам он никогда не истекает).
      required:
        - name
    IssuePatResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000PAT000000000001
        name:
          type: string
          example: laptop
        projectId:
          type: string
          example: 01JC4Z0000PROJETO000000001
        createdAt:
          type: string
          example: '2026-08-22T12:00:00.000Z'
          format: date-time
        expiresAt:
          type: object
          example: null
          nullable: true
          description: Нулевое = без срока действия.
        revokedAt:
          type: object
          example: null
          nullable: true
        lastUsedAt:
          type: object
          example: null
          nullable: true
          description: Нулевое = ни разу не использовался.
        token:
          type: string
          example: brb_9f8a...
          description: >-
            Сохраните сейчас — потом восстановить нельзя. Используйте в --token
            или BRABO_ACCOUNT_TOKEN.
      required:
        - id
        - name
        - projectId
        - createdAt
        - expiresAt
        - revokedAt
        - lastUsedAt
        - token
    ItemDeContratoInternalDto:
      type: object
      properties:
        tipo:
          type: string
          enum:
            - funcao
            - rota
            - evento
            - dado
          example: funcao
          description: >-
            Как другой модуль использует элемент: вызывает функцию, обращается к
            маршруту, подписывается на событие или строит форму данных.
        assinatura:
          type: string
          example: 'placePiece(board: Board, piece: Piece, pos: Pos): Board'
          description: Как другой модуль его использует. До 300 символов.
        descricao:
          type: string
          example: Returns a new board; never mutates the one passed in.
      required:
        - tipo
        - assinatura
    ItemDeContratoResponseDto:
      type: object
      properties:
        tipo:
          type: string
          enum:
            - funcao
            - rota
            - evento
            - dado
          example: funcao
        assinatura:
          type: string
          example: 'placePiece(board: Board, piece: Piece, pos: Pos): Board'
        descricao:
          type: string
          example: Returns a new board; never mutates the one passed in.
      required:
        - tipo
        - assinatura
        - descricao
    ItemDeDecisaoResponseDto:
      type: object
      properties:
        eventId:
          type: string
          example: evt_01JC4Z
        seq:
          type: number
          example: 43
        actorId:
          type: string
          example: criativo
        escolha:
          type: string
          nullable: true
          example: SQLite em arquivo
        contexto:
          type: string
          nullable: true
      required:
        - eventId
        - seq
        - actorId
        - escolha
        - contexto
    ItemDeRegraResponseDto:
      type: object
      properties:
        eventId:
          type: string
          example: evt_01JC4Z
        seq:
          type: number
          example: 42
        actorId:
          type: string
          example: po
        titulo:
          type: string
          nullable: true
          example: Login exige e-mail
        descricao:
          type: string
          nullable: true
        origens:
          type: number
          example: 2
          description: Сколько ссылок на источник цитирует правило.
      required:
        - eventId
        - seq
        - actorId
        - titulo
        - descricao
        - origens
    JwksResponseDto:
      type: object
      properties:
        keys:
          description: Активные публичные ключи Ed25519. Два во время ротации.
          type: array
          items:
            type: object
      required:
        - keys
    LastCallResponseDto:
      type: object
      properties:
        inputTokens:
          type: number
          example: 42000
          description: Input tokens of that call.
        modelName:
          type: string
          example: qwen2.5-coder:7b
        contextWindow:
          type: number
          example: 128000
          nullable: true
          description: The model's context window, or null when unknown.
      required:
        - inputTokens
        - modelName
        - contextWindow
    LlmMessageResponseDto:
      type: object
      properties:
        role:
          type: string
          example: assistant
          enum:
            - assistant
        content:
          type: string
          example: I'll read the file before answering.
        toolCalls:
          type: array
          items:
            $ref: '#/components/schemas/ToolCallResponseDto'
      required:
        - role
        - content
        - toolCalls
    LLMProviderCapabilitiesResponseDto:
      type: object
      properties:
        streaming:
          type: boolean
          example: true
        toolCalling:
          type: boolean
          example: true
        listModels:
          type: boolean
          example: true
          description: >-
            Провайдер может ПЕРЕЧИСЛИТЬ собственный каталог (синхронизация
            каталога).
        embeddings:
          type: boolean
          example: false
          description: Текст → вектор (ADR 0075). `true` только при доказательстве.
        routingPreference:
          type: boolean
          example: false
          description: >-
            Провайдер принимает ПРЕДПОЧТЕНИЕ МАРШРУТИЗАЦИИ (`price`,
            `throughput`, `latency`) для выбора среди upstream, обслуживающих
            одну и ту же модель (ADR 0166). `true` только после того, как smoke
            против реального API вернул выбранный upstream, — чтение
            документации не считается.
        stickySession:
          type: boolean
          example: false
          description: >-
            The provider pins a conversation to the upstream that served it when
            given a session key (ADR 0221). Only `true` after a smoke against
            the real API showed one upstream with the key and several without
            it.
        priceCeiling:
          type: boolean
          example: false
          description: >-
            The provider accepts a per-call PRICE CEILING (`provider.max_price`
            = catalog price x 1.5, ADR 0225): only an upstream within it serves
            the call. Only `true` after a smoke against the real API showed the
            served upstream within the ceiling.
        promptCaching:
          type: boolean
          example: false
          description: >-
            Провайдер принимает явные точки PROMPT-CACHE (`cache_control`) на
            стабильном префиксе вызова (RN-892). `true` только после того, как
            smoke против реального API показал закешированные входные токены при
            повторном вызове.
      required:
        - streaming
        - toolCalling
        - listModels
        - embeddings
        - routingPreference
        - stickySession
        - priceCeiling
        - promptCaching
    LlmTurnResponseDto:
      type: object
      properties:
        message:
          $ref: '#/components/schemas/LlmMessageResponseDto'
        usage:
          $ref: '#/components/schemas/LlmUsageResponseDto'
        error:
          type: object
          example: null
          nullable: true
          description: >-
            Сбой провайдера. Приходит в ТЕЛЕ с 200, а не как ошибка HTTP, потому
            что учёт в `usage` остаётся действительным — ход потратил, даже
            завершаясь сбоем.
        errorCode:
          type: object
          example: null
          nullable: true
          description: >-
            Нормализованный `code` сбоя провайдера (ADR 0041) — `auth`,
            `rate_limit`, `insufficient_credit` (HTTP 402, RN-730),
            `model_not_found`, `context_length`, `timeout`, `connection` или
            `upstream`. `null` без ошибки или когда ошибка не от провайдера
            (бюджет, отсутствующий binding). Решайте по нему, никогда по тексту
            `error`.
        modelName:
          type: object
          example: llama3.2:3b
          nullable: true
          description: >-
            Имя модели, сгенерировавшей ответ (вывод из проблемы 2), — `null`,
            когда ход завершился сбоем до разрешения модели (нет binding или
            binding на несуществующую модель).
        truncated:
          type: boolean
          enum:
            - true
          description: >-
            Присутствует (всегда `true`) только когда провайдер обрезал ответ на
            лимите вывода (`finish_reason: "length"` / `stop_reason:
            "max_tokens"`, RN-737): текст и аргументы `toolCalls` усечены.
            Engine отказывается выполнять усечённый вызов инструмента и сообщает
            об обрезке.
        toolRouting:
          description: >-
            Шаг, на котором Jev выбрал инструмент (ADR 0179, RN-625).
            Отсутствует, когда маршрутизатор не запрашивался (провайдер не
            OpenRouter, меньше двух инструментов, переключатель рабочего
            пространства выключен). Engine сообщает об этом как
            `tool_router.decided`.
          allOf:
            - $ref: '#/components/schemas/ToolRoutingResponseDto'
      required:
        - message
        - usage
        - error
        - errorCode
        - modelName
    LlmTurnStreamEventResponseDto:
      type: object
      properties:
        type:
          type: string
          enum:
            - delta
            - done
            - error
          example: delta
          description: >-
            `delta` несёт `text`; `done` закрывает поток с `usage`; `error`
            несёт `message`.
        text:
          type: string
          example: I'll read the
        usage:
          $ref: '#/components/schemas/LlmUsageResponseDto'
        message:
          type: string
          example: provider unavailable
        modelName:
          type: object
          example: llama3.2:3b
          nullable: true
          description: >-
            Только в кадре `done`/`final` — имя модели, сгенерировавшей ответ
            (вывод из проблемы 2). `null`, когда ход завершился сбоем до
            разрешения модели.
        truncated:
          type: boolean
          enum:
            - true
          description: >-
            Только в кадре `final`, и только когда провайдер обрезал ответ на
            лимите вывода (RN-737).
      required:
        - type
    LlmUsageResponseDto:
      type: object
      properties:
        inputTokens:
          type: number
          example: 1820
        outputTokens:
          type: number
          example: 340
        costMicros:
          type: number
          example: 52700
          description: >-
            Стоимость в микро-USD: РЕАЛЬНАЯ стоимость, которую вернул провайдер,
            если вернул (`usage.cost` у OpenRouter), иначе замороженная цена
            каталога (ADR 0188, RN-665).
        estimated:
          type: boolean
          example: false
          description: >-
            `true`, когда провайдер не вернул счётчик и стоимость была ОЦЕНЕНА.
            Число стоит меньше, и потребитель должен уметь об этом сказать.
      required:
        - inputTokens
        - outputTokens
        - costMicros
        - estimated
    LocalFolderFileDto:
      type: object
      properties:
        path:
          type: string
          example: src/domain/rag/chunking.ts
          description: >-
            Относительный путь внутри выбранной папки — никогда не абсолютный,
            никогда не `..`.
        content:
          type: string
          description: Простой текст UTF-8 — никогда не base64.
      required:
        - path
        - content
    LocalizacaoDoProjetoResponseDto:
      type: object
      properties:
        tipo:
          type: string
          enum:
            - gerenciada
            - montada
            - indisponivel
          example: gerenciada
          description: >-
            Какой из корней broker разрешает эту папку. `gerenciada` →
            `PROJECT_WORKSPACES_HOST_ROOT` (папка, управляемая продуктом,
            названная по `workspaceDirName`); `montada` →
            `BRABO_PROJECTS_HOST_BASE` (единая база проектов Mounted, ADR 0141);
            `indisponivel` → ни один корень этого сервера до неё не достаёт, и
            `motivo` говорит почему. Три состояния, а не два: проекты `runner` и
            устаревшие проекты `mounted`, созданные вне базы, — это отказы с
            РАЗНЫМИ способами исправления, и их слияние в null заставило бы
            вызывающего гадать, какой он получил.
        segmento:
          type: string
          example: loja
          description: >-
            Часть, которую корень broker НЕ покрывает, присоединяемая к нему как
            `<root>/<segmento>`. Для `gerenciada` это `workspaceDirName`; для
            `montada` — ОТНОСИТЕЛЬНЫЙ путь внутри базы (может содержать `/`).
            ОТСУТСТВУЕТ — а не null, — когда `tipo` равен `indisponivel`: два
            варианта — дискриминированное объединение на стороне api, и протокол
            говорит то же самое, поэтому вызывающий, читающий `segmento` без
            проверки `tipo`, ломается громко, а не составляет `<root>/null`.
        motivo:
          type: string
          example: o projeto está no modo "runner"…
          description: >-
            Почему ни один корень не достаёт до этой папки. Присутствует только
            когда `tipo` равен `indisponivel`. Это сообщение, предназначенное
            для передачи тому, кто управляет, поэтому оно называет режим или
            базу.
      required:
        - tipo
    LoginDto:
      type: object
      properties:
        email:
          type: string
          example: fulano@brabo.dev
        senha:
          type: string
          example: correct horse battery staple
      required:
        - email
        - senha
    MachineDeviceKeyInternalDto:
      type: object
      properties:
        name:
          type: string
          example: servidor-de-casa
          description: >-
            Имя, чтобы человек потом узнал эту МАШИНУ в списке ключей устройств
            каждого проекта, который она обслуживает. Не уникально. Установщик
            предлагает hostname; api ничего из него здесь не выводит.
        publicKeyJwk:
          type: string
          example: '{"kty":"OKP","crv":"Ed25519","x":"…"}'
          description: >-
            ПУБЛИЧНЫЙ JWK Ed25519 (RFC 8037), сериализованный как JSON. Пара
            генерируется на машине, и приватная половина никогда не передаётся —
            JWK с `d` отклоняется с 400 с указанием этого и никогда не
            сохраняется.
      required:
        - name
        - publicKeyJwk
    MachineDeviceKeyInternalResponseDto:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: 01JC4Z0000CHAVE000000000001
          description: >-
            Id регистрации. Запишите его в ПРИВАТНЫЙ JWK как `kid` перед
            сохранением файла (RN-475): это единственная связь между ключом на
            диске и публичной половиной на сервере, и каждый последующий шаг
            лишь передаёт его дальше.
        userId:
          type: string
          format: uuid
          example: 01JC4Z0000USUARIO0000000001
          description: >-
            Владелец, которого ОПРЕДЕЛИЛА api, — единственный пользователь
            установки. Вызывающий его не выбирает и не отправляет; это поле
            здесь, чтобы установщик мог сверить его с только что созданной
            учётной записью, а не предполагать.
        name:
          type: string
          example: servidor-de-casa
        createdAt:
          type: string
          example: '2026-09-12T12:00:00.000Z'
          format: date-time
        replacedKeyIds:
          example: []
          description: >-
            Id МАШИННЫХ ключей, которые этот ЗАМЕНИЛ, — отозваны в той же
            транзакции. Пусто — обычный случай (новая установка). Непусто
            означает, что локальный агент, всё ещё держащий один из них,
            перестаёт получать тикеты, и установщик должен об этом сказать, а не
            оставлять это на обнаружение. Ключи проекта (ADR 0118) никогда не
            затрагиваются.
          type: array
          items:
            type: string
      required:
        - id
        - userId
        - name
        - createdAt
        - replacedKeyIds
    MarkTaskInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        agentId:
          type: string
          example: dev-api
          description: Двигать задачу может только агент, который её забрал.
        status:
          type: string
          enum:
            - todo
            - in_progress
            - in_review
            - done
          example: in_review
      required:
        - projectId
        - agentId
        - status
    MensagemAoAgenteResponseDto:
      type: object
      properties:
        ok:
          type: boolean
          example: true
          description: Всегда `true`; сбой становится ошибкой HTTP.
        mensagemId:
          type: string
          example: 01JC4Z0000EVENTO000000000001
          description: >-
            Id события `chat.message`, записанного для этого сообщения, — id,
            которым его отменяют, пока оно ждёт в очереди.
        entrega:
          type: string
          enum:
            - lida
            - enfileirada
          example: enfileirada
          description: >-
            `lida`: агент сразу начал с ним ход. `enfileirada`: агент был
            посреди хода, поэтому сообщение попало в его очередь и будет
            прочитано вместе с другими поставленными в очередь за ОДИН ход,
            когда текущий закончится (RN-673). Журнал сессии несёт
            `chat.message_queued` / `chat.message_delivered` /
            `chat.message_cancelled`.
        posicao:
          type: number
          example: 2
          description: >-
            Позиция в очереди (1 = следующее). Присутствует, только когда
            `entrega` равен `enfileirada`.
      required:
        - ok
        - mensagemId
        - entrega
    MirrorSyncResultInternalDto:
      type: object
      properties:
        ok:
          type: boolean
          example: true
          description: >-
            РЕАЛЬНЫЙ исход копирования, сообщённый локальным агентом после
            завершения, — никогда не оптимистичное «ok» до окончания раунда.
        destination:
          type: string
          example: /home/you/mirrors/store
          description: >-
            Назначение, которое раунд фактически разрешил (успех) или пытался
            использовать (сбой), на машине ПОЛЬЗОВАТЕЛЯ. ЗАМОРОЖЕНО в строке:
            показ текущего назначения проекта рядом со вчерашней копией был бы
            утверждением экрана о папке, которой тот раунд не касался. Опущено,
            когда сбой касается самого назначения.
        filesCopied:
          type: number
          example: 412
          description: >-
            Обычные файлы, скопированные в этом раунде. `0` — это число, а не
            отсутствие: «синхронизировано и ничего не скопировано» — отдельное
            состояние (RN-088).
        filesSkipped:
          type: number
          example: 3
          description: >-
            Записи, перечисленные git, которые не являются обычными файлами, —
            вложенный репозиторий (worktree dev agent — один из них),
            символическая ссылка или файл, исчезнувший между перечислением и
            копированием.
        filesRefused:
          type: number
          example: 0
          description: >-
            Цели, отклонённые пофайловой защитой (символическая ссылка,
            выходящая за пределы назначения). Подсчитываются, никогда не
            замалчиваются.
        error:
          type: string
          example: >-
            o espelho não conseguiu listar o trabalho com o git: not a git
            repository
          description: >-
            ИМЕНОВАННОЕ сообщение о сбое. При большой длине хранится обрезанным,
            с указанием на обрезку. Оно никогда не стирает последнюю успешную
            синхронизацию — какая строка ТЕКУЩАЯ, решается сравнением двух меток
            времени.
      required:
        - ok
    MirrorSyncResultResponseDto:
      type: object
      properties:
        recorded:
          type: boolean
          example: true
        status:
          type: string
          example: synced
          enum:
            - never
            - synced
            - failed
          description: >-
            Какое из трёх состояний ТЕКУЩЕЕ после этой записи. `never` здесь
            недостижимо (каждая запись ставит одну из двух меток времени) и
            указано, потому что словарь тот же, что читает экран.
      required:
        - recorded
        - status
    ModelBindingResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000BINDING00000000001
        scope:
          type: string
          enum:
            - workspace
            - project
            - area
            - agent
            - session
          example: project
        scopeId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
          description: >-
            Id рабочего пространства, проекта или сессии, к которым привязан
            binding. В областях действия `agent` и `area`, существующих НА
            ПРОЕКТ, он составной: `<projectId>:<agent slug|area key>` (ADR
            0064).
        modelId:
          type: string
          example: 01JC4Z0000MODELO00000000001
        routingPreference:
          type: string
          enum:
            - price
            - throughput
            - latency
          nullable: true
          example: null
          description: >-
            Как хаб выбирает upstream для этого binding (ADR 0166, RN-583).
            `null` = хаб решает сам. Задаётся только для провайдера,
            объявляющего возможность `routingPreference`.
        createdBy:
          type: string
          example: 01JC4Z0000USUARIO0000000001
        createdAt:
          type: string
          example: '2026-07-11T09:00:00.000Z'
          format: date-time
        updatedAt:
          type: string
          example: '2026-07-11T09:00:00.000Z'
          format: date-time
      required:
        - id
        - scope
        - scopeId
        - modelId
        - routingPreference
        - createdBy
        - createdAt
        - updatedAt
    ModelComCuradoriaResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000MODELO00000000001
        provider:
          type: string
          enum:
            - ollama
            - anthropic
            - openai
          example: anthropic
        name:
          type: string
          example: claude-opus-4-8
          description: Идентификатор у провайдера.
        displayName:
          type: string
          example: Claude Opus 4.8
        inputPricePerMillionMicros:
          type: number
          example: 15000000
          description: Цена за миллион ВХОДНЫХ токенов, в микро-USD.
        outputPricePerMillionMicros:
          type: number
          example: 75000000
          description: Цена за миллион ВЫХОДНЫХ токенов, в микро-USD.
        contextWindow:
          type: object
          example: 200000
          nullable: true
          description: Также возможность модели `context_length`.
        supportsToolCalling:
          type: boolean
          example: true
          description: >-
            НАТИВНЫЙ вызов инструментов. Без этого модель только для чата и не
            может быть привязана к агенту (RN-040).
        supportsStreaming:
          type: boolean
          example: true
        supportsVision:
          type: boolean
          example: false
          description: >-
            Принимает ИЗОБРАЖЕНИЕ на вход. `false` означает, что провайдер не
            объявил модальность, — а не что модель этого не умеет (ADR 0041).
        supportsReasoning:
          type: boolean
          example: true
          description: >-
            Принимает явное рассуждение (thinking). В OpenRouter берётся из
            `supported_parameters: reasoning`.
        generatesImage:
          type: boolean
          example: false
          description: >-
            ПРОИЗВОДИТ изображение — ось, отличная от приёма на вход. Это есть у
            немногих моделей, и смешение двух отправляет пользователя к неверной
            модели.
        reasoningBudget:
          type: number
          nullable: true
          example: 2048
          description: >-
            Reasoning budget (tokens) the model is PROVEN to respect, curated by
            hand (ADR 0223). Null = not proven; the field is not sent.
        manualPricing:
          type: boolean
          example: true
          description: >-
            Цена, введённая из документации провайдера, а не синхронизированная.
            Синхронизация цен не перезаписывает помеченную строку без явного
            решения.
        availability:
          type: string
          enum:
            - available
            - unavailable
          example: available
          description: >-
            УДАЛЁННАЯ реальность, наблюдаемая синхронизацией, ось, независимая
            от курации. `unavailable` — модель, исчезнувшая из каталога
            провайдера, — она никогда не удаляется, потому что на неё указывают
            binding и история затрат.
        billing:
          type: string
          enum:
            - api
            - subscription
            - free
          example: api
          description: >-
            CURATED billing mark (AT-632, RN-954): `subscription` is a seat —
            its cost is shown apart from the US$ paid. Never set by sync.
        lastSeenAt:
          type: object
          example: '2026-07-30T03:00:00.000Z'
          format: date-time
          nullable: true
          description: >-
            Последний раз, когда синхронизация видела модель в каталоге
            провайдера.
        createdAt:
          type: string
          example: '2026-07-01T00:00:00.000Z'
          format: date-time
        updatedAt:
          type: string
          example: '2026-07-01T00:00:00.000Z'
          format: date-time
        isActive:
          type: boolean
          example: true
          description: >-
            Курация ВЛАДЕЛЬЦЕМ **этого рабочего пространства**: деактивированная
            модель исчезает из селектора, но остаётся в исторических затратах. У
            модели, обнаруженной синхронизацией, нет строки курации, а
            отсутствие строки означает `false`.
        uses:
          type: array
          example:
            - codigo
          description: >-
            Для чего ЭТО рабочее пространство использует модель. Мнение
            оператора, а не возможность провайдера, — ни один каталог не
            публикует «хорош для кода». Пустой список означает «ни у кого нет
            мнения», а не «не подходит».
          items:
            type: string
            enum:
              - codigo
              - documentacao
              - analise
              - imagem
              - conversa
        freeRoutingAlias:
          type: boolean
          example: false
          description: >-
            Псевдоним свободной маршрутизации OpenRouter (id, начинающийся с
            `~`): цена каталога — витринная, а счёт идёт от того upstream,
            который обслужил вызов. Курация отказывается его АКТИВИРОВАТЬ (422
            `alias_de_roteamento_livre`); активированный до этого правила
            остаётся активным, помеченный этим флагом, а после деактивации
            вернуться не может. Выводится из провайдера и имени при чтении,
            никогда не хранится.
      required:
        - id
        - provider
        - name
        - displayName
        - inputPricePerMillionMicros
        - outputPricePerMillionMicros
        - contextWindow
        - supportsToolCalling
        - supportsStreaming
        - supportsVision
        - supportsReasoning
        - generatesImage
        - reasoningBudget
        - manualPricing
        - availability
        - billing
        - lastSeenAt
        - createdAt
        - updatedAt
        - isActive
        - uses
        - freeRoutingAlias
    ModeloDoCatalogoResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000MODELO00000000001
        provider:
          type: string
          enum:
            - ollama
            - anthropic
            - openai
          example: anthropic
        name:
          type: string
          example: claude-opus-4-8
          description: Идентификатор у провайдера.
        displayName:
          type: string
          example: Claude Opus 4.8
        inputPricePerMillionMicros:
          type: number
          example: 15000000
          description: Цена за миллион ВХОДНЫХ токенов, в микро-USD.
        outputPricePerMillionMicros:
          type: number
          example: 75000000
          description: Цена за миллион ВЫХОДНЫХ токенов, в микро-USD.
        contextWindow:
          type: object
          example: 200000
          nullable: true
          description: Также возможность модели `context_length`.
        supportsToolCalling:
          type: boolean
          example: true
          description: >-
            НАТИВНЫЙ вызов инструментов. Без этого модель только для чата и не
            может быть привязана к агенту (RN-040).
        supportsStreaming:
          type: boolean
          example: true
        supportsVision:
          type: boolean
          example: false
          description: >-
            Принимает ИЗОБРАЖЕНИЕ на вход. `false` означает, что провайдер не
            объявил модальность, — а не что модель этого не умеет (ADR 0041).
        supportsReasoning:
          type: boolean
          example: true
          description: >-
            Принимает явное рассуждение (thinking). В OpenRouter берётся из
            `supported_parameters: reasoning`.
        generatesImage:
          type: boolean
          example: false
          description: >-
            ПРОИЗВОДИТ изображение — ось, отличная от приёма на вход. Это есть у
            немногих моделей, и смешение двух отправляет пользователя к неверной
            модели.
        reasoningBudget:
          type: number
          nullable: true
          example: 2048
          description: >-
            Reasoning budget (tokens) the model is PROVEN to respect, curated by
            hand (ADR 0223). Null = not proven; the field is not sent.
        manualPricing:
          type: boolean
          example: true
          description: >-
            Цена, введённая из документации провайдера, а не синхронизированная.
            Синхронизация цен не перезаписывает помеченную строку без явного
            решения.
        availability:
          type: string
          enum:
            - available
            - unavailable
          example: available
          description: >-
            УДАЛЁННАЯ реальность, наблюдаемая синхронизацией, ось, независимая
            от курации. `unavailable` — модель, исчезнувшая из каталога
            провайдера, — она никогда не удаляется, потому что на неё указывают
            binding и история затрат.
        billing:
          type: string
          enum:
            - api
            - subscription
            - free
          example: api
          description: >-
            CURATED billing mark (AT-632, RN-954): `subscription` is a seat —
            its cost is shown apart from the US$ paid. Never set by sync.
        lastSeenAt:
          type: object
          example: '2026-07-30T03:00:00.000Z'
          format: date-time
          nullable: true
          description: >-
            Последний раз, когда синхронизация видела модель в каталоге
            провайдера.
        createdAt:
          type: string
          example: '2026-07-01T00:00:00.000Z'
          format: date-time
        updatedAt:
          type: string
          example: '2026-07-01T00:00:00.000Z'
          format: date-time
        isActive:
          type: boolean
          example: true
          description: >-
            Курация ВЛАДЕЛЬЦЕМ **этого рабочего пространства**: деактивированная
            модель исчезает из селектора, но остаётся в исторических затратах. У
            модели, обнаруженной синхронизацией, нет строки курации, а
            отсутствие строки означает `false`.
        uses:
          type: array
          example:
            - codigo
          description: >-
            Для чего ЭТО рабочее пространство использует модель. Мнение
            оператора, а не возможность провайдера, — ни один каталог не
            публикует «хорош для кода». Пустой список означает «ни у кого нет
            мнения», а не «не подходит».
          items:
            type: string
            enum:
              - codigo
              - documentacao
              - analise
              - imagem
              - conversa
        freeRoutingAlias:
          type: boolean
          example: false
          description: >-
            Псевдоним свободной маршрутизации OpenRouter (id, начинающийся с
            `~`): цена каталога — витринная, а счёт идёт от того upstream,
            который обслужил вызов. Курация отказывается его АКТИВИРОВАТЬ (422
            `alias_de_roteamento_livre`); активированный до этого правила
            остаётся активным, помеченный этим флагом, а после деактивации
            вернуться не может. Выводится из провайдера и имени при чтении,
            никогда не хранится.
        sugestao:
          nullable: true
          description: >-
            SUGGESTION only (derived on read, never stored): billing band from
            the 9Router alias and the manufacturer price from a curated table.
            Applying it is a curation gesture. Null for OpenRouter and unlisted
            ids.
          type: object
          allOf:
            - $ref: '#/components/schemas/SugestaoDoCatalogoResponseDto'
      required:
        - id
        - provider
        - name
        - displayName
        - inputPricePerMillionMicros
        - outputPricePerMillionMicros
        - contextWindow
        - supportsToolCalling
        - supportsStreaming
        - supportsVision
        - supportsReasoning
        - generatesImage
        - reasoningBudget
        - manualPricing
        - availability
        - billing
        - lastSeenAt
        - createdAt
        - updatedAt
        - isActive
        - uses
        - freeRoutingAlias
        - sugestao
    ModelPriceChangeResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000PRECO000000000001
        modelId:
          type: string
          example: 01JC4Z0000MODELO00000000001
        inputBeforeMicros:
          type: number
          example: 2500000
          description: Микро-USD за 1M, до.
        inputAfterMicros:
          type: number
          example: 3000000
          description: Микро-USD за 1M, после.
        outputBeforeMicros:
          type: number
          example: 10000000
        outputAfterMicros:
          type: number
          example: 12000000
        source:
          type: string
          enum:
            - manual
            - sync
          example: manual
          description: >-
            `manual` — кто-то ввёл из документации провайдера; `sync` — пришло
            из удалённого каталога.
        changedBy:
          type: object
          example: 01JC4Z0000USUARIO0000000001
          nullable: true
          description: '`null`, когда пришло из синхронизации, — за этим нет человека.'
        createdAt:
          type: string
          example: '2026-08-01T12:00:00.000Z'
          format: date-time
      required:
        - id
        - modelId
        - inputBeforeMicros
        - inputAfterMicros
        - outputBeforeMicros
        - outputAfterMicros
        - source
        - changedBy
        - createdAt
    ModelPullRequestResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b
        workspaceId:
          type: string
          example: 9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b
        requestedBy:
          type: string
          example: 9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b
        repoId:
          type: string
          example: meta-llama/Llama-3.1-8B-Instruct-GGUF
        estimatedSizeBytes:
          type: object
          example: null
          nullable: true
          description: >-
            Присутствует, только когда Hub опубликовал `usedStorage` в поиске, —
            никогда не оценивается наугад.
        status:
          type: string
          enum:
            - pending_confirmation
            - confirmed
            - pulling
            - active
            - failed
          example: pending_confirmation
          description: >-
            `pending_confirmation` → `confirmed` → `pulling` → `active` |
            `failed`. Первые два состояния И ЕСТЬ второе явное подтверждение,
            которого продукт требует перед запуском любого скачивания.
        confirmedAt:
          type: object
          example: null
          nullable: true
          format: date-time
          description: Когда произошёл клик подтверждения.
        failedReason:
          type: object
          example: null
          nullable: true
          description: >-
            Только в `failed`, с префиксом источника (infra | модель | код |
            политика) — никогда молчаливый сбой.
        createdAt:
          type: string
          example: '2026-08-26T12:00:00.000Z'
          format: date-time
        updatedAt:
          type: string
          example: '2026-08-26T12:00:00.000Z'
          format: date-time
      required:
        - id
        - workspaceId
        - requestedBy
        - repoId
        - estimatedSizeBytes
        - status
        - confirmedAt
        - failedReason
        - createdAt
        - updatedAt
    ModelResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000MODELO00000000001
        provider:
          type: string
          enum:
            - ollama
            - anthropic
            - openai
          example: anthropic
        name:
          type: string
          example: claude-opus-4-8
          description: Идентификатор у провайдера.
        displayName:
          type: string
          example: Claude Opus 4.8
        inputPricePerMillionMicros:
          type: number
          example: 15000000
          description: Цена за миллион ВХОДНЫХ токенов, в микро-USD.
        outputPricePerMillionMicros:
          type: number
          example: 75000000
          description: Цена за миллион ВЫХОДНЫХ токенов, в микро-USD.
        contextWindow:
          type: object
          example: 200000
          nullable: true
          description: Также возможность модели `context_length`.
        supportsToolCalling:
          type: boolean
          example: true
          description: >-
            НАТИВНЫЙ вызов инструментов. Без этого модель только для чата и не
            может быть привязана к агенту (RN-040).
        supportsStreaming:
          type: boolean
          example: true
        supportsVision:
          type: boolean
          example: false
          description: >-
            Принимает ИЗОБРАЖЕНИЕ на вход. `false` означает, что провайдер не
            объявил модальность, — а не что модель этого не умеет (ADR 0041).
        supportsReasoning:
          type: boolean
          example: true
          description: >-
            Принимает явное рассуждение (thinking). В OpenRouter берётся из
            `supported_parameters: reasoning`.
        generatesImage:
          type: boolean
          example: false
          description: >-
            ПРОИЗВОДИТ изображение — ось, отличная от приёма на вход. Это есть у
            немногих моделей, и смешение двух отправляет пользователя к неверной
            модели.
        reasoningBudget:
          type: number
          nullable: true
          example: 2048
          description: >-
            Reasoning budget (tokens) the model is PROVEN to respect, curated by
            hand (ADR 0223). Null = not proven; the field is not sent.
        manualPricing:
          type: boolean
          example: true
          description: >-
            Цена, введённая из документации провайдера, а не синхронизированная.
            Синхронизация цен не перезаписывает помеченную строку без явного
            решения.
        availability:
          type: string
          enum:
            - available
            - unavailable
          example: available
          description: >-
            УДАЛЁННАЯ реальность, наблюдаемая синхронизацией, ось, независимая
            от курации. `unavailable` — модель, исчезнувшая из каталога
            провайдера, — она никогда не удаляется, потому что на неё указывают
            binding и история затрат.
        billing:
          type: string
          enum:
            - api
            - subscription
            - free
          example: api
          description: >-
            CURATED billing mark (AT-632, RN-954): `subscription` is a seat —
            its cost is shown apart from the US$ paid. Never set by sync.
        lastSeenAt:
          type: object
          example: '2026-07-30T03:00:00.000Z'
          format: date-time
          nullable: true
          description: >-
            Последний раз, когда синхронизация видела модель в каталоге
            провайдера.
        createdAt:
          type: string
          example: '2026-07-01T00:00:00.000Z'
          format: date-time
        updatedAt:
          type: string
          example: '2026-07-01T00:00:00.000Z'
          format: date-time
      required:
        - id
        - provider
        - name
        - displayName
        - inputPricePerMillionMicros
        - outputPricePerMillionMicros
        - contextWindow
        - supportsToolCalling
        - supportsStreaming
        - supportsVision
        - supportsReasoning
        - generatesImage
        - reasoningBudget
        - manualPricing
        - availability
        - billing
        - lastSeenAt
        - createdAt
        - updatedAt
    ModuleMapResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000MODULEMAP00000001
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        sessionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
        modules:
          type: array
          items:
            $ref: '#/components/schemas/ModuleNodeResponseDto'
        version:
          type: number
          example: 3
          description: >-
            История неизменяема: каждая новая карта — ещё одна версия, а текущая
            — та, у которой наибольший `version`.
        createdAt:
          type: string
          example: '2026-07-24T16:00:00.000Z'
          format: date-time
      required:
        - id
        - projectId
        - sessionId
        - modules
        - version
        - createdAt
    ModuleNodeResponseDto:
      type: object
      properties:
        name:
          type: string
          example: api
        stack:
          type: string
          example: NestJS + Drizzle
        responsibility:
          type: string
          example: Business rules and HTTP surface.
        dependsOn:
          example:
            - db
          description: >-
            Имена других модулей. Цикл здесь приводит к ОТКЛОНЕНИЮ карты при
            записи.
          type: array
          items:
            type: string
        resources:
          description: >-
            Что нужно ЭТОМУ модулю самому по себе внутри контейнера проекта
            (RN-683, ADR 0199). Отсутствует, если Архитектор не объявил. Infra
            запускает контейнер с СУММОЙ по модулям, не ниже значения по
            умолчанию, пока есть необъявленный модуль.
          allOf:
            - $ref: '#/components/schemas/RecursosDoContainerResponseDto'
      required:
        - name
        - stack
        - responsibility
        - dependsOn
    MySpendResponseDto:
      type: object
      properties:
        projectId:
          type: string
          example: 9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b
          format: uuid
        dias:
          type: number
          example: 30
        actorId:
          type: string
          example: 9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b
          format: uuid
          description: >-
            Актор отчёта — всегда тот, кто вызвал. Строка другого актора не
            попадает.
        totalMicros:
          type: number
          example: 1250000
        assinaturaMicros:
          type: number
          example: 0
          description: >-
            Part of the cost from models curated as `subscription` (RN-954): a
            seat, shown apart from the US$ paid (`cost - subscription`).
        inputTokens:
          type: number
          example: 120000
        outputTokens:
          type: number
          example: 35000
        chamadas:
          type: number
          example: 42
        porSessao:
          type: array
          items:
            $ref: '#/components/schemas/SpendLinhaResponseDto'
        porDia:
          type: array
          items:
            $ref: '#/components/schemas/SpendPorDiaResponseDto'
      required:
        - projectId
        - dias
        - actorId
        - totalMicros
        - assinaturaMicros
        - inputTokens
        - outputTokens
        - chamadas
        - porSessao
        - porDia
    MyWorkspaceSpendResponseDto:
      type: object
      properties:
        workspaceId:
          type: string
          format: uuid
        dias:
          type: number
          example: 30
        actorId:
          type: string
          format: uuid
          description: Always whoever called.
        totalMicros:
          type: number
          example: 1250000
        assinaturaMicros:
          type: number
          example: 0
          description: Seat part of the total (RN-954).
        inputTokens:
          type: number
          example: 120000
        outputTokens:
          type: number
          example: 35000
        chamadas:
          type: number
          example: 42
        porProjeto:
          description: By PROJECT.
          type: array
          items:
            $ref: '#/components/schemas/SpendLinhaResponseDto'
        porDia:
          type: array
          items:
            $ref: '#/components/schemas/SpendPorDiaResponseDto'
      required:
        - workspaceId
        - dias
        - actorId
        - totalMicros
        - assinaturaMicros
        - inputTokens
        - outputTokens
        - chamadas
        - porProjeto
        - porDia
    ObservacaoDeContainerResponseDto:
      type: object
      properties:
        containerId:
          type: string
          example: c0ffeebabe...
        nome:
          type: string
          example: brabo-exp002-f52be111
        estado:
          type: string
          enum:
            - created
            - running
            - paused
            - restarting
            - removing
            - exited
            - dead
          example: running
          description: Собственное состояние демона, без перевода и без слияния.
        imagem:
          type: string
          example: node:22-bookworm-slim
        iniciadoEm:
          type: object
          nullable: true
          format: date-time
          description: '`null`, когда контейнер ни разу не запускался.'
      required:
        - containerId
        - nome
        - estado
        - imagem
        - iniciadoEm
    OfertaDeHandoffResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000HANDOFF00000000001
        sessionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        fromAgent:
          type: string
          example: criativo
          description: Slug агента, который передал эстафету.
        toAgent:
          type: string
          example: po
          description: Slug принимающего агента.
        artifactId:
          type: object
          example: 01JC4Z0000ARTEFATO000000001
          nullable: true
          description: Артефакт, ставший причиной handoff (product_brief, module_map…).
        status:
          type: string
          enum:
            - offered
            - accepted
            - completed
            - rejected
            - superseded
          example: offered
          description: >-
            Это поле ИЗМЕНЯЕМО — это текущее состояние. Каждый переход также
            становится неизменяемым событием `handoff.*` в журнале, где и живёт
            история. `superseded` (ADR 0182, RN-635): предложение перестало быть
            текущим — целевой агент был активирован другим путём, или более
            новое предложение той же цели в проекте заменило его
            (`handoff.superseded`). Принять можно только `offered`.
        createdAt:
          type: string
          example: '2026-07-24T10:00:00.000Z'
          format: date-time
        updatedAt:
          type: string
          example: '2026-07-24T10:05:00.000Z'
          format: date-time
        desfecho:
          type: string
          enum:
            - criado
            - substituiu_oferta
            - ja_oferecido
          example: criado
          description: >-
            `criado`: новое предложение. `substituiu_oferta`: новое предложение,
            а ожидающее(ие) той же цели стало(и) `superseded`. `ja_oferecido`:
            новой строки нет — возвращается уже существующее ожидающее
            предложение (та же сессия и нет нового артефакта, или `seAusente`).
      required:
        - id
        - sessionId
        - projectId
        - fromAgent
        - toAgent
        - artifactId
        - status
        - createdAt
        - updatedAt
        - desfecho
    OfertaInternaDeHandoffResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000HANDOFF00000000001
        sessionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        fromAgent:
          type: string
          example: criativo
          description: Slug агента, который передал эстафету.
        toAgent:
          type: string
          example: po
          description: Slug принимающего агента.
        artifactId:
          type: object
          example: 01JC4Z0000ARTEFATO000000001
          nullable: true
          description: Артефакт, ставший причиной handoff (product_brief, module_map…).
        status:
          type: string
          enum:
            - offered
            - accepted
            - completed
            - rejected
            - superseded
          example: offered
          description: >-
            Это поле ИЗМЕНЯЕМО — это текущее состояние. Каждый переход также
            становится неизменяемым событием `handoff.*` в журнале, где и живёт
            история. `superseded` (ADR 0182, RN-635): предложение перестало быть
            текущим — целевой агент был активирован другим путём, или более
            новое предложение той же цели в проекте заменило его
            (`handoff.superseded`). Принять можно только `offered`.
        createdAt:
          type: string
          example: '2026-07-24T10:00:00.000Z'
          format: date-time
        updatedAt:
          type: string
          example: '2026-07-24T10:05:00.000Z'
          format: date-time
        desfecho:
          type: string
          enum:
            - criado
            - substituiu_oferta
            - ja_oferecido
          example: criado
          description: >-
            `criado`: новое предложение. `substituiu_oferta`: новое предложение,
            а ожидающее(ие) той же цели стало(и) `superseded`. `ja_oferecido`:
            новой строки нет — возвращается уже существующее ожидающее
            предложение (та же сессия и нет нового артефакта, или `seAusente`).
        aceiteAutomatico:
          $ref: '#/components/schemas/AceiteAutomaticoResponseDto'
      required:
        - id
        - sessionId
        - projectId
        - fromAgent
        - toAgent
        - artifactId
        - status
        - createdAt
        - updatedAt
        - desfecho
        - aceiteAutomatico
    OkResponseDto:
      type: object
      properties:
        ok:
          type: boolean
          example: true
          description: Всегда `true`; сбой становится ошибкой HTTP.
      required:
        - ok
    OpenGateInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        agentId:
          type: string
          example: dev-api
      required:
        - projectId
        - agentId
    OpenQuestionAffectsDto:
      type: object
      properties:
        regras:
          type: array
          items:
            type: string
        historias:
          type: array
          items:
            type: string
        tarefas:
          type: array
          items:
            type: string
      required:
        - regras
        - historias
        - tarefas
    OpenQuestionAnswerDto:
      type: object
      properties:
        texto:
          type: string
        aceitouSugestao:
          type: boolean
        decisionEventId:
          type: string
        respondidaPor:
          type: string
        respondidaEm:
          type: string
          format: date-time
      required:
        - texto
        - aceitouSugestao
        - decisionEventId
        - respondidaPor
        - respondidaEm
    OpenQuestionResponseDto:
      type: object
      properties:
        questionId:
          type: string
          format: uuid
        eventId:
          type: string
        sessionId:
          type: string
        pergunta:
          type: string
        sugestao:
          type: string
          nullable: true
        afeta:
          $ref: '#/components/schemas/OpenQuestionAffectsDto'
        autor:
          type: string
          example: criativo
          description: Агент, который спросил.
        criadaEm:
          type: string
          format: date-time
        status:
          type: string
          enum:
            - open
            - answered
        autorDaSessao:
          type: string
          nullable: true
          description: >-
            Пользователь, создавший исходную сессию, — единственный, кто может
            ответить.
        resposta:
          nullable: true
          type: object
          allOf:
            - $ref: '#/components/schemas/OpenQuestionAnswerDto'
      required:
        - questionId
        - eventId
        - sessionId
        - pergunta
        - sugestao
        - afeta
        - autor
        - criadaEm
        - status
        - autorDaSessao
        - resposta
    OpenQuestionsListResponseDto:
      type: object
      properties:
        itens:
          type: array
          items:
            $ref: '#/components/schemas/OpenQuestionResponseDto'
        total:
          type: number
          description: Итог по фильтру; `itens` ограничен 50 (RN-180).
      required:
        - itens
        - total
    PaginaDeAcoesResponseDto:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/ProposedActionResponseDto'
        nextCursor:
          type: object
          example: 7
          nullable: true
          description: >-
            Передайте как `afterSeq` на следующей странице; `null` означает
            конец списка.
      required:
        - items
        - nextCursor
    PaginaDeEventosResponseDto:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/SessionEventResponseDto'
        nextCursor:
          type: object
          example: 41
          nullable: true
          description: >-
            Передайте как `afterSeq` для следующей страницы. `null` означает
            конец журнала.
      required:
        - items
        - nextCursor
    PassoDeBootstrapResponseDto:
      type: object
      properties:
        step:
          type: string
          enum:
            - commit_pr_template
            - commit_branching_policy
            - commit_gitignore
            - create_dev_branch
            - create_qa_branch
            - create_rc_branch
            - protect_branches
          example: create_dev_branch
        status:
          type: string
          enum:
            - pending
            - running
            - done
            - failed
          example: running
      required:
        - step
        - status
    PedidoDeParalelismoResponseDto:
      type: object
      properties:
        estado:
          type: string
          enum:
            - executado
            - aguardando_autorizacao
            - recusado
          example: aguardando_autorizacao
          description: >-
            `executado`: агент запущен, в пределах лимита lead.
            `aguardando_autorizacao`: запрос стал `proposed_action`, и НИЧЕГО не
            запущено, пока вы не решите. `recusado`: у области недопустимый
            `max_parallel`.
        actionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
          description: >-
            Действие, по которому нужно решить. Только в
            `aguardando_autorizacao`.
        ativosNaSessao:
          type: number
          example: 2
          description: Dev agent, уже запущенные В СЕССИИ, суммарно по всем модулям.
        maxParallel:
          type: number
          example: 2
          description: Лимит, который lead использует без вопросов.
        motivo:
          type: string
          description: Почему запрос отклонён. Только в `recusado`.
      required:
        - estado
    PerfilApagadoResponseDto:
      type: object
      properties:
        deleted:
          type: number
          example: 2
          description: Сколько профилей удалено.
        optedOut:
          type: boolean
          example: true
          description: >-
            Всегда `true`: удаление БЕЗ записи отказа от профилирования было бы
            косметическим, потому что следующий раунд вывел бы всё заново.
      required:
        - deleted
        - optedOut
    PerfilOptInResponseDto:
      type: object
      properties:
        optedOut:
          type: boolean
          example: false
      required:
        - optedOut
    PermissionsFileResponseDto:
      type: object
      properties:
        allow:
          example:
            - Terminal(pnpm test:*)
            - Terminal(pnpm lint)
          description: Автоматически одобряемые шаблоны.
          type: array
          items:
            type: string
        deny:
          example:
            - Terminal(rm -rf *)
            - Terminal(git push --force*)
          description: >-
            Запрещённые шаблоны. `deny` побеждает `allow` и автономию агента —
            никакая комбинация настроек не разрешает перечисленное здесь.
          type: array
          items:
            type: string
        ask:
          example:
            - Terminal(pnpm add *)
          description: Шаблоны, всегда требующие решения человека.
          type: array
          items:
            type: string
      required:
        - allow
        - deny
        - ask
    PersonalAccessTokenAdminResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000PAT000000000001
        name:
          type: string
          example: laptop
        projectId:
          type: string
          example: 01JC4Z0000PROJETO000000001
        createdAt:
          type: string
          example: '2026-08-22T12:00:00.000Z'
          format: date-time
        expiresAt:
          type: object
          example: null
          nullable: true
          description: Нулевое = без срока действия.
        revokedAt:
          type: object
          example: null
          nullable: true
        lastUsedAt:
          type: object
          example: null
          nullable: true
          description: Нулевое = ни разу не использовался.
        userId:
          type: string
          example: 01JC4Z0000USUARIO000000001
        userEmail:
          type: string
          example: dev@exemplo.com
      required:
        - id
        - name
        - projectId
        - createdAt
        - expiresAt
        - revokedAt
        - lastUsedAt
        - userId
        - userEmail
    PersonalAccessTokenResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000PAT000000000001
        name:
          type: string
          example: laptop
        projectId:
          type: string
          example: 01JC4Z0000PROJETO000000001
        createdAt:
          type: string
          example: '2026-08-22T12:00:00.000Z'
          format: date-time
        expiresAt:
          type: object
          example: null
          nullable: true
          description: Нулевое = без срока действия.
        revokedAt:
          type: object
          example: null
          nullable: true
        lastUsedAt:
          type: object
          example: null
          nullable: true
          description: Нулевое = ни разу не использовался.
      required:
        - id
        - name
        - projectId
        - createdAt
        - expiresAt
        - revokedAt
        - lastUsedAt
    PortaDto:
      type: object
      properties:
        host:
          type: number
          example: 8080
        container:
          type: number
          example: 3000
        protocol:
          type: string
          example: tcp
      required:
        - host
        - container
        - protocol
    PrecoSugeridoResponseDto:
      type: object
      properties:
        fabricante:
          type: string
          example: Anthropic
        entradaMicros:
          type: number
          example: 100000
          description: micro-USD per 1M tokens.
        saidaMicros:
          type: number
          example: 500000
          description: micro-USD per 1M tokens.
        fonte:
          type: string
          example: https://docs.claude.com/en/docs/about-claude/pricing.md
        conferidoEm:
          type: string
          example: '2026-11-01'
          description: Date the row was checked.
        observacao:
          type: string
          example: Prompt up to 100,000 tokens.
      required:
        - fabricante
        - entradaMicros
        - saidaMicros
        - fonte
        - conferidoEm
    PriorHypothesisResponseDto:
      type: object
      properties:
        agenteAlvo:
          type: string
          example: dev-api
        hipotese:
          type: string
          example: The instructions don't say when the task is done.
        sugestao:
          type: string
          example: Add an explicit definition-of-done criterion.
        confiancaPercent:
          type: number
          example: 72
      required:
        - agenteAlvo
        - hipotese
        - sugestao
        - confiancaPercent
    ProductMetricsDeploymentDayResponseDto:
      type: object
      properties:
        dia:
          type: string
          example: '2026-08-01'
        merges:
          type: number
          example: 2
      required:
        - dia
        - merges
    ProductMetricsFunnelResponseDto:
      type: object
      properties:
        etapas:
          type: array
          items:
            $ref: '#/components/schemas/ProductMetricsFunnelStageResponseDto'
        sessoesComCommit:
          type: array
          items:
            type: string
        sessoesComPr:
          type: array
          items:
            type: string
        sessoesComMerge:
          type: array
          items:
            type: string
      required:
        - etapas
        - sessoesComCommit
        - sessoesComPr
        - sessoesComMerge
    ProductMetricsFunnelStageResponseDto:
      type: object
      properties:
        etapa:
          type: string
          example: sessão produziu commit
        sessoes:
          type: number
          example: 3
        taxaDaEtapaAnterior:
          type: object
          example: 0.67
          nullable: true
          description: '`null` на первом этапе — «конверсии из» чего-либо нет.'
      required:
        - etapa
        - sessoes
        - taxaDaEtapaAnterior
    ProductMetricsLeadTimeResponseDto:
      type: object
      properties:
        sessionId:
          type: string
          example: 01JC4Z0000SESSAO000000000001
        primeiroCommitEm:
          type: string
          example: '2026-08-01T10:00:00.000Z'
        primeiroMergeEm:
          type: string
          example: '2026-08-01T12:00:00.000Z'
        leadTimeMs:
          type: number
          example: 7200000
      required:
        - sessionId
        - primeiroCommitEm
        - primeiroMergeEm
        - leadTimeMs
    ProductMetricsLeadTimesResponseDto:
      type: object
      properties:
        perSession:
          type: array
          items:
            $ref: '#/components/schemas/ProductMetricsLeadTimeResponseDto'
        averageMs:
          type: object
          example: 7200000
          nullable: true
          description: >-
            Простое среднее в мс — `null`, если нет сессии и с коммитом, И с
            merge.
      required:
        - perSession
        - averageMs
    ProductMetricsProjectResponseDto:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        name:
          type: string
          example: exp003
      required:
        - id
        - name
    ProductMetricsResponseDto:
      type: object
      properties:
        project:
          $ref: '#/components/schemas/ProductMetricsProjectResponseDto'
        totalActionsConsidered:
          type: number
          example: 18
          description: Учтённые git-действия (git_commit/pr_open/git_merge, любой статус).
        funnel:
          $ref: '#/components/schemas/ProductMetricsFunnelResponseDto'
        leadTimes:
          $ref: '#/components/schemas/ProductMetricsLeadTimesResponseDto'
        deploymentFrequency:
          type: array
          items:
            $ref: '#/components/schemas/ProductMetricsDeploymentDayResponseDto'
      required:
        - project
        - totalActionsConsidered
        - funnel
        - leadTimes
        - deploymentFrequency
    ProficiencyDraftDto:
      type: object
      properties:
        userId:
          type: string
          format: uuid
          example: 01JC4Z0000USUARIO0000000002
        competency:
          type: string
          example: TypeScript
          description: >-
            Должна быть в каталоге, возвращаемом `anamnese-context`, —
            выдуманная компетенция ОТКЛОНЯЕТСЯ. Каталог зависит от `module_map`,
            поэтому проверка на уровне домена, а не DTO.
        level:
          type: string
          enum:
            - iniciante
            - intermediario
            - avancado
          example: avancado
        rationale:
          type: string
          example: Fixed three generic typing errors without help.
        evidenceEventIds:
          example:
            - 01JC4Z8QK3M7YV2N5T9B0PXHRB
          description: >-
            Каждый id должен быть из события сессии ЭТОГО проекта — ссылка на
            событие другого проекта отклоняется.
          type: array
          items:
            type: string
      required:
        - userId
        - competency
        - level
        - rationale
        - evidenceEventIds
    ProficiencyProfileResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000PERFIL00000000001
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        userId:
          type: string
          example: 01JC4Z0000USUARIO0000000002
        competency:
          type: string
          example: TypeScript
        level:
          type: string
          enum:
            - iniciante
            - intermediario
            - avancado
          example: avancado
        rationale:
          type: string
          example: Fixed three generic typing errors without help.
          description: >-
            «Почему» за уровнем. Без этого профиль был бы вердиктом без права на
            обжалование.
        evidenceEventIds:
          example:
            - 01JC4Z8QK3M7YV2N5T9B0PXHRB
          description: События журнала, подтверждающие уровень.
          type: array
          items:
            type: string
        createdAt:
          type: string
          example: '2026-07-26T12:00:00.000Z'
          format: date-time
        updatedAt:
          type: string
          example: '2026-07-26T12:00:00.000Z'
          format: date-time
        userName:
          type: object
          example: Senior Dev
          nullable: true
        userEmail:
          type: object
          example: dev@brabo.dev
          nullable: true
          description: >-
            `null`, когда владелец профиля больше не участник проекта, — профиль
            переживает удаление участника.
      required:
        - id
        - projectId
        - userId
        - competency
        - level
        - rationale
        - evidenceEventIds
        - createdAt
        - updatedAt
        - userName
        - userEmail
    ProjectBlockedStatusResponseDto:
      type: object
      properties:
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        blockedTaskCount:
          type: number
          example: 2
      required:
        - projectId
        - blockedTaskCount
    ProjectBusinessRuleResponseDto:
      type: object
      properties:
        id:
          type: string
          example: evt_01jc4z0000regra0000000001
          description: >-
            Id СОБЫТИЯ `artifact.business_rule`. Именно это значение идёт в
            `business_rule_ids` при создании истории — таблицы правил нет.
        title:
          type: string
          example: Cart accepts at most 50 items
        description:
          type: string
          example: Above that, the addition is refused with 409.
          description: >-
            Содержание правила, а не только формулировка: из него берётся
            требование истории. Именно это поле отличает это чтение от покрытия
            на экране.
        coveredByStoryIds:
          example:
            - 01JC4Z0000HISTORIA000000001
          description: Истории, которые уже ссылаются на это правило.
          type: array
          items:
            type: string
        covered:
          type: boolean
          example: true
      required:
        - id
        - title
        - description
        - coveredByStoryIds
        - covered
    ProjectBusinessRulesResponseDto:
      type: object
      properties:
        rules:
          type: array
          items:
            $ref: '#/components/schemas/ProjectBusinessRuleResponseDto'
        uncoveredCount:
          type: number
          example: 2
          description: >-
            Сколько правил не покрывает ни одна история. Это незакрытое PO и
            число, которое инструмент `listar_regras_de_negocio` ставит перед
            моделью.
      required:
        - rules
        - uncoveredCount
    ProjectCardBudgetResponseDto:
      type: object
      properties:
        limitMicros:
          type: number
          example: 50000000
        spentMicros:
          type: number
          example: 12500000
      required:
        - limitMicros
        - spentMicros
    ProjectCardSummaryResponseDto:
      type: object
      properties:
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        provider:
          type: string
          enum:
            - local
            - github
            - gitlab
          example: github
          description: '`local`, когда у проекта ещё нет созданного репозитория.'
        provisioningStatus:
          type: string
          enum:
            - provisioning
            - provisioned
            - provision_failed
            - awaiting_plan_decision
          nullable: true
          example: provisioned
          description: '`null`, когда bootstrap ни разу не запускался. Выводится из курсора.'
        budget:
          nullable: true
          description: >-
            `null`, когда у проекта НИКОГДА не был задан бюджет, — в отличие от
            обнулённой строки, и именно это заставляет карточку предлагать
            «Задать бюджет».
          type: object
          allOf:
            - $ref: '#/components/schemas/ProjectCardBudgetResponseDto'
        latestSessionId:
          type: object
          example: 01JC4Z0000SESSAO00000000001
          nullable: true
        latestSeq:
          type: number
          example: 41
          description: >-
            Последний `seq`, уже записанный в самой свежей сессии (`nextSeq -
            1`); 0, когда сессии нет. Веб сравнивает это с уже увиденным, чтобы
            посчитать непрочитанные.
        lastEvent:
          nullable: true
          description: Последнее событие самой свежей сессии — строка в подвале карточки.
          type: object
          allOf:
            - $ref: '#/components/schemas/SessionEventResponseDto'
        storiesAwaitingPromotion:
          type: number
          example: 2
          description: >-
            Истории, которые PO завершил и которые ждут продвижения
            пользователем (RN-048).
        pendingApprovalsCount:
          type: number
          example: 8
          description: >-
            `proposed_actions` со `status = pending` по ВСЕМУ проекту, во всех
            сессиях, — не только в самой свежей. Именно это боковая панель
            показывает как значок проекта (RN-151).
        onlineAgentCount:
          type: number
          example: 2
          description: >-
            Сколько агентов В СЕТИ прямо сейчас — работают или имеют незакрытое,
            ожидающее решения (RN-409). Никогда не размер команды: dev agent
            `idle`/`idle_tripped` не считается, разговорный агент `idle` не
            считается, QA/SecOps никогда не считаются (один вердикт на вызов).
        roster:
          $ref: '#/components/schemas/RosterFactsResponseDto'
      required:
        - projectId
        - provider
        - provisioningStatus
        - budget
        - latestSessionId
        - latestSeq
        - lastEvent
        - storiesAwaitingPromotion
        - pendingApprovalsCount
        - onlineAgentCount
        - roster
    ProjectFoldersResponseDto:
      type: object
      properties:
        base:
          type: object
          example: /home/voce/brabo
          nullable: true
          description: >-
            База смонтированных проектов этой установки (`BRABO_PROJECTS_BASE`,
            ADR 0141) — то же значение, что возвращает `GET .../projects-base`,
            повторённое здесь, чтобы клиент мог отрисовать хлебные крошки без
            второго вызова. `null` означает, что установка не предлагает режим
            Mounted.
        path:
          type: object
          example: /home/voce/brabo/clientes
          nullable: true
          description: >-
            Фактически перечисленная папка, нормализованная. Равна `base`, когда
            запрос опустил `path`. `null`, только когда базы нет вовсе: ничего
            не перечислено, потому что негде перечислять.
        entries:
          example:
            - api
            - loja
            - website
          description: >-
            Имена ПОДКАТАЛОГОВ непосредственно в `path`, отсортированные,
            никогда рекурсивно. Файлы, символические ссылки и записи с точкой в
            начале исключены — см. `arquivos`, `simbolicos` и `truncado`.
          type: array
          items:
            type: string
        truncado:
          type: boolean
          example: false
          description: >-
            В папке больше 500 подкаталогов, и вернулись только первые 500.
            Сортировка происходит ДО обрезки, поэтому обрезка детерминирована, а
            не «что файловая система вернула первым».
        arquivos:
          type: number
          example: 12
          description: >-
            Сколько записей, не являющихся каталогами, осталось за бортом.
            Папка, полная кода, не должна выглядеть пустой (RN-180).
        simbolicos:
          type: number
          example: 1
          description: >-
            Сколько символических ссылок осталось за бортом. Обозреватель
            никогда по ним не переходит — ссылка сообщается, но в неё не
            спускаются, поэтому ссылка за пределы базы не является выходом из
            неё.
      required:
        - base
        - path
        - entries
        - truncado
        - arquivos
        - simbolicos
    ProjectGitRemoteResponseDto:
      type: object
      properties:
        kind:
          type: string
          example: remote
          enum:
            - local
            - remote
          description: >-
            `local` — путь к bare-репозиторию на диске без учётных данных;
            `remote` — URL, требующий аутентификации.
        origin:
          type: string
          example: https://github.com/daneiel/hello-api.git
          description: >-
            Что становится `git remote add origin <this>`. **Без встроенных
            учётных данных**, по замыслу: это значение записывается в
            `.git/config` рабочей области и читаемо любым, у кого есть доступ к
            каталогу проекта.
        defaultBranch:
          type: string
          example: main
        token:
          type: string
          nullable: true
          description: >-
            Только при `remote`. Токен ВЛАДЕЛЬЦА рабочего пространства (RN-058),
            расшифрованный в момент вызова. Получатель внедряет его при каждом
            вызове и отбрасывает.
        username:
          type: string
          nullable: true
          example: x-access-token
          description: Пользователь пары HTTP Basic; токен — пароль.
      required:
        - kind
        - origin
        - defaultBranch
    ProjectMemberComUsuarioResponseDto:
      type: object
      properties:
        userId:
          type: string
          example: 01JC4Z0000USUARIO0000000002
        role:
          type: string
          enum:
            - viewer
            - developer
            - maintainer
            - owner
          example: maintainer
          description: >-
            ЭФФЕКТИВНАЯ роль: роль проекта, если она есть, иначе унаследованная
            от рабочего пространства (RN-471) — роль проекта ПЕРЕКРЫВАЕТ роль
            рабочего пространства в обе стороны, вверх или вниз.
        name:
          type: object
          example: Senior Dev
          nullable: true
        email:
          type: string
          example: dev@brabo.dev
      required:
        - userId
        - role
        - name
        - email
    ProjectMemberResponseDto:
      type: object
      properties:
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        userId:
          type: string
          example: 01JC4Z0000USUARIO0000000002
        role:
          type: string
          enum:
            - viewer
            - developer
            - maintainer
            - owner
          example: maintainer
          description: >-
            Линейная иерархия: owner > maintainer > developer > viewer. Каждая
            роль включает разрешения последующих.
        createdAt:
          type: string
          example: '2026-07-22T08:05:00.000Z'
          format: date-time
      required:
        - projectId
        - userId
        - role
        - createdAt
    ProjectMirrorStateResponseDto:
      type: object
      properties:
        mirrorPath:
          type: object
          example: /home/you/mirrors/store
          nullable: true
          description: >-
            Назначение, объявленное СЕГОДНЯ (`projects.mirror_path`, RN-515).
            `null` означает, что у проекта нет зеркала, — НОРМАЛЬНОЕ состояние,
            и именно поэтому экран полностью скрывает эту строку, а не
            выдумывает отсутствие.
        status:
          type: string
          example: synced
          enum:
            - never
            - synced
            - failed
          description: >-
            `never` — ни один runner ещё не сообщил о раунде (строки нет).
            `synced` — последний раунд скопировал; `filesCopied` может быть `0`,
            что значит «посмотрел, и копировать было нечего», и для этого есть
            своя фраза. `failed` — последний раунд завершился сбоем, и ошибка
            новее последнего успеха. API выводит это из двух меток времени,
            чтобы у правила был один источник; экран всё равно получает сырые
            поля, чтобы написать фразу.
        lastSyncedAt:
          type: object
          example: '2026-09-07T12:04:00.000Z'
          nullable: true
          description: >-
            Последняя УСПЕШНАЯ синхронизация. Последующий сбой никогда её не
            стирает — это самое полезное, что есть у этого экрана, пока зеркало
            сломано.
        filesCopied:
          type: object
          example: 412
          nullable: true
        filesSkipped:
          type: object
          example: 3
          nullable: true
        filesRefused:
          type: object
          example: 0
          nullable: true
        lastDestination:
          type: object
          example: /home/you/mirrors/store
          nullable: true
          description: >-
            Куда фактически писал последний раунд — ЗАМОРОЖЕНО. Расходится с
            `mirrorPath` после того, как кто-то меняет назначение, потому что
            разрешение передаётся в join и меняется только при переподключении
            runner (RN-516).
        lastError:
          type: object
          example: null
          nullable: true
        lastErrorAt:
          type: object
          example: null
          nullable: true
      required:
        - mirrorPath
        - status
        - lastSyncedAt
        - filesCopied
        - filesSkipped
        - filesRefused
        - lastDestination
        - lastError
        - lastErrorAt
    ProjectResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        workspaceId:
          type: string
          example: 01JC4Z0000WORKSPACE00000001
        name:
          type: string
          example: Checkout
        slug:
          type: string
          example: checkout
          description: Уникален в пределах рабочего пространства.
        workspaceDirName:
          type: string
          example: checkout-3f2b1c8e
          description: >-
            Имя физической папки рабочей области этого проекта в
            PROJECT_WORKSPACES_ROOT (RN-109). `<slug>-<8 id chars>` у нового
            проекта; сырой UUID у проекта, созданного до появления этого
            столбца. Заморожено при создании — последующее редактирование `slug`
            его не пересчитывает.
        executionMode:
          type: string
          enum:
            - container
            - mounted
            - runner
          example: container
          description: >-
            ГДЕ выполняется команда (RN-169/RN-421 — ADR 0072/0104).
            `container`: папка, управляемая в PROJECT_WORKSPACES_ROOT.
            `mounted`: папка пользователя в `workspacePath`, внутри
            `BRABO_PROJECTS_BASE`, доступная через bind-mount базы (ADR 0141).
            `runner`: папка пользователя, подтверждённая runner. И `mounted`, и
            `runner` сообщают подтверждение в `workspaceVerifiedAt`.
        workspacePath:
          type: object
          example: null
          nullable: true
          description: >-
            Абсолютный путь к папке пользователя — заполнен для
            `mounted`/`runner`, всегда `null` в режиме `container`.
        workspaceVerifiedAt:
          type: object
          example: null
          nullable: true
          description: >-
            Когда путь был подтверждён на диске. `null` = ещё не проверен. В
            `runner` подтверждение приходит от подключения CLI (RN-423); в
            `mounted` ставится, когда Infra запускает контейнер и папка
            материализуется (RN-501, ADR 0142). `container` никогда не заполняет
            это поле. Записывает ОДНО подтверждение, никогда не heartbeat.
        mirrorPath:
          type: object
          example: null
          nullable: true
          description: >-
            Назначение зеркала на машине ПОЛЬЗОВАТЕЛЯ — папка ВНЕ смонтированной
            базы, куда локальный агент копирует работу (RN-515, ADR 0147).
            `null` означает, что у проекта нет зеркала, и это НОРМАЛЬНОЕ
            состояние, никогда не ошибка. Оно может быть только у
            `mounted`/`runner`. Задаётся через `PUT
            /projects/:projectId/mirror-path`; передаётся при каждом чтении
            проекта, чтобы ничему не требовался отдельный endpoint. Значение
            проверено только ЛЕКСИЧЕСКИ — существует ли папка, знает локальный
            агент, а не этот API.
        language:
          type: string
          example: pt-BR
          description: >-
            Язык ПРОЕКТА (RN-619): язык всего, у чего нет автора-человека, —
            общие артефакты (product brief, бизнес-правила, ADR) и ходы, которые
            никто не набирал (kickoff, dev agent, gate, сообщения коммитов, тела
            PR). Канонический код BCP-47, открытый список. Новый проект начинает
            с эффективного языка ответов своего создателя (RN-618). Задаётся
            через `PATCH /projects/:projectId` (`maintainer`). Engine отправляет
            его модели как эфемерное системное сообщение в конце каждого вызова
            LLM хода без автора-человека (RN-622); артефакт, выпущенный во время
            хода человека, по-прежнему следует языку ответов этого человека.
        createdBy:
          type: string
          example: 01JC4Z0000USUARIO0000000001
        taskBudgetMicros:
          type: object
          example: 500000
          nullable: true
          description: >-
            Лимит токенов на задачу dev agent, в микро-USD. `null` использует
            значение домена по умолчанию.
        maxConsecutiveBlocked:
          type: object
          example: 3
          nullable: true
          description: >-
            Предохранитель на dev agent (Фаза 12b — RN-047): последовательные
            задачи, закончившиеся блокировкой, до остановки в idle_tripped.
            `null` использует значение домена по умолчанию.
        storyPromotion:
          type: string
          enum:
            - manual
            - auto
          example: auto
          description: >-
            Кто продвигает историю в `ready` (Фаза 12c — RN-048). `manual`: PO
            предлагает, пользователь решает. `auto`: автоматическое продвижение
            при создании — значение по умолчанию для новых проектов начиная с
            RN-659; проекты, созданные раньше, сохраняют своё значение.
        createdAt:
          type: string
          example: '2026-07-21T11:00:00.000Z'
          format: date-time
        updatedAt:
          type: string
          example: '2026-07-21T11:00:00.000Z'
          format: date-time
      required:
        - id
        - workspaceId
        - name
        - slug
        - workspaceDirName
        - executionMode
        - workspacePath
        - workspaceVerifiedAt
        - mirrorPath
        - language
        - createdBy
        - taskBudgetMicros
        - maxConsecutiveBlocked
        - storyPromotion
        - createdAt
        - updatedAt
    ProjectsBaseResponseDto:
      type: object
      properties:
        projectsBase:
          type: object
          example: /home/voce/brabo
          nullable: true
          description: >-
            Единственная папка на компьютере оператора, которую видят контейнеры
            Brabo, смонтированная по ИДЕНТИЧНОСТИ (`$X:$X`) в `api` и `engine`
            (ADR 0141). `null` означает, что `BRABO_PROJECTS_BASE` не задана:
            установка не предлагает режим Mounted, и мастер проекта не должен
            его предлагать. Никогда не сбой — отсутствие является нормальным
            состоянием.
        brokerConfigurado:
          type: boolean
          example: false
          description: >-
            Настроен ли у этой установки broker контейнеров (`BROKER_URL` задан,
            ADR 0130). Проекты `container` и `mounted` поднимают контейнер
            только через broker (ADR 0144), и без него каждый dev agent
            заблокирован навсегда (ADR 0143). При `false` мастер проекта не
            выбирает заранее Mounted, оставляет Container и Mounted видимыми, но
            недоступными для выбора, с причиной в тексте, и заранее выбирает
            Runner (ADR 0161, RN-573). Говорит, что переменная существует, и
            никогда — что broker отвечает. Конфигурация установки, как и
            `projectsBase`: одинакова для каждого рабочего пространства.
      required:
        - projectsBase
        - brokerConfigurado
    ProjectSpendTeamResponseDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
        dias:
          type: number
          example: 30
        totalMicros:
          type: number
          example: 1250000
        assinaturaMicros:
          type: number
          example: 0
          description: Seat part of the total (RN-954).
        inputTokens:
          type: number
          example: 120000
        outputTokens:
          type: number
          example: 35000
        chamadas:
          type: number
          example: 42
        porAgente:
          description: By AGENT.
          type: array
          items:
            $ref: '#/components/schemas/SpendLinhaResponseDto'
        porArea:
          description: By catalog AREA of the agent; `sem-area` when none.
          type: array
          items:
            $ref: '#/components/schemas/SpendLinhaResponseDto'
      required:
        - projectId
        - dias
        - totalMicros
        - assinaturaMicros
        - inputTokens
        - outputTokens
        - chamadas
        - porAgente
        - porArea
    ProjectUnreadEventsResponseDto:
      type: object
      properties:
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        sessionId:
          type: string
          example: 01JC4Z0000SESSAO00000000001
          description: >-
            САМАЯ СВЕЖАЯ сессия проекта — та же, что `projects-summary` сообщает
            в `latestSessionId`.
        events:
          description: >-
            В порядке УБЫВАНИЯ `seq` — первый элемент самый свежий (RN-100). Не
            более 50 на проект, тот же лимит, что `GET .../events` применяет без
            `limit`, и когда непрочитанных больше, возвращаются САМЫЕ НОВЫЕ.
            Сколько осталось за бортом, получается из `latestSeq` минус граница,
            без дополнительного запроса.
          type: array
          items:
            $ref: '#/components/schemas/SessionEventResponseDto'
      required:
        - projectId
        - sessionId
        - events
    PromocaoRecusadaResponseDto:
      type: object
      properties:
        storyId:
          type: string
          example: 01JC2XK9Q7
        reason:
          type: string
          example: 'Story is not ready: missing dod, dor'
          description: >-
            Понятная причина. История могла потерять готовность, или один из её
            модулей мог выпасть из `module_map` между предложением и решением.
      required:
        - storyId
        - reason
    PromoteStoriesDto:
      type: object
      properties:
        storyIds:
          example:
            - 01JC2XK9Q7
            - 01JC2XKB4M
          description: >-
            Id историй для продвижения в `ready`. Одна — это пакет из 1. Каждая
            история проверяется и продвигается сама по себе — неудача одной не
            блокирует остальные.
          type: array
          items:
            type: string
      required:
        - storyIds
    PromoteStoriesResponseDto:
      type: object
      properties:
        promoted:
          description: Id, перешедшие в `ready`, с уже доступными для взятия задачами.
          type: array
          items:
            type: string
        failed:
          description: Пусто, когда весь пакет прошёл.
          type: array
          items:
            $ref: '#/components/schemas/PromocaoRecusadaResponseDto'
      required:
        - promoted
        - failed
    PromptTemplateReadResponseDto:
      type: object
      properties:
        name:
          type: string
          example: dev-agent-kickoff
        version:
          type: string
          example: '3'
        body:
          type: string
          example: You are the dev agent for the {{modulo}} module...
        hash:
          type: string
          example: sha256:9f2c...
      required:
        - name
        - version
        - body
        - hash
    PromptTemplateResponseDto:
      type: object
      properties:
        name:
          type: string
          example: dev-agent-kickoff
        version:
          type: string
          example: '3'
        body:
          type: string
          example: You are the dev agent for the {{modulo}} module...
        hash:
          type: string
          example: sha256:9f2c...
        active:
          type: boolean
          description: >-
            Является ли это текущей версией шаблона. Всегда `true` сразу после
            upsert, создавшего новую версию; может вернуться `false`, когда
            попадание идемпотентности (тот же хеш) указывает на версию, которую
            ПОЗДНЕЙШАЯ публикация уже деактивировала.
      required:
        - name
        - version
        - body
        - hash
        - active
    ProposeActionDto:
      type: object
      properties:
        actionType:
          type: string
          enum:
            - terminal
            - git_commit
            - git_push
            - pr_open
            - spend
            - git_repo_create
            - git_branch_create
            - git_branch_protect
            - write_file
            - open_adr_pr
            - git_merge
            - open_infra_pr
            - instruction_patch
            - parallelize
            - raise_max_parallel
            - propose_execution_plan
            - assess_implementability
            - container_start
            - container_stop
            - container_remove
            - container_recreate
            - container_start_via_runner
            - qa_environment_start
          example: terminal
          description: >-
            Тип действия. У каждого типа своя минимальная роль: `git_push`,
            `git_merge` и `git_branch_protect` требуют `maintainer`, остальные —
            `developer`.
        actor:
          description: Кто предлагает.
          allOf:
            - $ref: '#/components/schemas/ActorDto'
        payload:
          type: object
          additionalProperties: true
          example:
            command: pnpm test
          description: Параметры действия, с формой, своей для каждого `actionType`.
      required:
        - actionType
        - actor
        - payload
    ProposedActionResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRC
          description: ULID действия.
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        sessionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
        seq:
          type: number
          example: 7
          description: Порядок действия внутри сессии; используется как курсор в списках.
        actionType:
          type: string
          enum:
            - terminal
            - git_commit
            - git_push
            - pr_open
            - spend
            - git_repo_create
            - git_branch_create
            - git_branch_protect
            - write_file
            - open_adr_pr
            - git_merge
            - open_infra_pr
            - instruction_patch
            - parallelize
            - raise_max_parallel
            - propose_execution_plan
            - assess_implementability
            - container_start
            - container_stop
            - container_remove
            - container_recreate
            - container_start_via_runner
            - qa_environment_start
          example: terminal
          description: >-
            Что сделало бы действие. `git_push` и `git_merge` требуют
            `maintainer`.
        payload:
          type: object
          additionalProperties: true
          example:
            command: pnpm test
          description: Параметры действия, свои для каждого `actionType`.
        status:
          type: string
          enum:
            - pending
            - approved
            - denied
            - auto_approved
            - executed
            - failed
          example: pending
          description: >-
            Состояние в конвейере. `auto_approved` намеренно отличается от
            `approved`: журналу нужно различать, что решил человек и что
            разрешила политика.
        resolvedPolicy:
          type: string
          enum:
            - auto_approve
            - require_approval
            - deny
          example: require_approval
          description: >-
            Что решил `permissions.json` для этого действия. `deny` ВСЕГДА
            побеждает `allow` — даже автономия агента его не переопределяет.
        actor:
          description: Кто предложил.
          allOf:
            - $ref: '#/components/schemas/ActorResponseDto'
        decidedBy:
          type: object
          example: null
          nullable: true
          description: Id пользователя, который одобрил или отклонил. Никогда не агент.
        decidedAt:
          type: object
          example: null
          format: date-time
          nullable: true
        rejectionReason:
          type: object
          example: null
          nullable: true
        executionResult:
          type: object
          additionalProperties: true
          nullable: true
          example: null
          description: >-
            Результат выполнения, с формой, своей для каждого `actionType`, —
            вывод и код выхода для терминала, номер и URL PR для git-типов.
            `null`, пока действие не выполнено.
        createdAt:
          type: string
          example: '2026-07-27T14:33:10.900Z'
          format: date-time
        updatedAt:
          type: string
          example: '2026-07-27T14:33:10.900Z'
          format: date-time
      required:
        - id
        - projectId
        - sessionId
        - seq
        - actionType
        - payload
        - status
        - resolvedPolicy
        - actor
        - decidedBy
        - decidedAt
        - rejectionReason
        - executionResult
        - createdAt
        - updatedAt
    ProposeHypothesesInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        tier:
          type: string
          enum:
            - leve
            - pesada
          example: leve
          description: >-
            Два уровня используют действительно разные модели; стоимость
            расходится.
        triggeredBy:
          type: string
          enum:
            - auto
            - manual
          example: auto
        eventCount:
          type: number
          example: 128
          description: Размер проанализированного журнала.
        cause:
          type: string
          enum:
            - normal
            - timeout
            - kill
            - crash
            - node_shutdown
            - unknown
          example: timeout
          description: >-
            Причина, КЛАССИФИЦИРОВАННАЯ engine. Именно она, а не терминальный
            статус, решает, обязателен ли `terminationAnalysis`. Необязательна,
            чтобы не сломать более старый engine во время поэтапного
            развёртывания.
        hypotheses:
          type: array
          items:
            $ref: '#/components/schemas/HypothesisDraftDto'
      required:
        - projectId
        - tier
        - triggeredBy
        - eventCount
        - hypotheses
    ProposeHypothesesResponseDto:
      type: object
      properties:
        analysisId:
          type: string
          example: 01JC4Z0000ANALISE0000000001
          description: Созданный раунд. Предыдущий раунд этой сессии становится замещённым.
        hypotheses:
          type: array
          items:
            $ref: '#/components/schemas/HypothesisResponseDto'
      required:
        - analysisId
        - hypotheses
    ProposeInstructionPatchInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        agent:
          type: string
          example: dev-api
        proposedContent:
          type: string
          example: |-
            # dev-api

            Always run the suite before opening the PR.
          description: ВЕСЬ файл в итоговом виде, а не diff.
        rationale:
          type: string
          example: The task was reopened three times for lack of a criterion.
        hypothesisId:
          type: string
          format: uuid
          example: 01JC4Z0000HIPOTESE000000001
          description: Гипотеза, ставшая причиной патча. Именно это замыкает цикл Анамнеза.
      required:
        - projectId
        - agent
        - proposedContent
        - rationale
    ProposeMaxParallelInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        area:
          type: string
          example: dev
          description: Область, чей лимит повысился бы.
        proposto:
          type: number
          example: 4
          minimum: 1
          description: >-
            Предлагаемый лимит. Должен быть ВЫШЕ текущего — предложение того же
            или меньшего превращается в шум в очереди, которую пользователю
            нужно читать, а Анамнез запускается периодически.
        rationale:
          type: string
          example: >-
            You authorized the same request four times in this window, and none
            was denied.
          description: Основано на НАБЛЮДАЕМЫХ РЕШЕНИЯХ, а не на впечатлении.
      required:
        - projectId
        - area
        - proposto
        - rationale
    ProviderCapabilitiesResponseDto:
      type: object
      properties:
        provider:
          type: string
          enum:
            - ollama
            - anthropic
            - openai
            - openrouter
            - nvidia-nim
            - together
            - deepinfra
            - bitdeer
            - vultr
            - 9router
            - opencode-zen
          example: openrouter
        capabilities:
          $ref: '#/components/schemas/LLMProviderCapabilitiesResponseDto'
      required:
        - provider
        - capabilities
    ProvisionedRepositoryResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000REPOSITORIO000001
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        provider:
          type: string
          enum:
            - local
            - github
            - gitlab
          example: github
        externalId:
          type: string
          example: '904837261'
          description: Id репозитория у провайдера.
        url:
          type: string
          example: https://github.com/acme/checkout
        defaultBranch:
          type: string
          example: main
          description: >-
            Политика проекта использует `dev`, `qa` и `main` как постоянные
            ветки.
        visibility:
          type: string
          enum:
            - public
            - private
          example: private
        origin:
          type: string
          enum:
            - created
            - adopted
          example: created
          description: >-
            `created` = Brabo создал репозиторий; `adopted` = указал на уже
            существовавший. Неизменяемо после записи (RN-046).
        provisionedBy:
          type: string
          example: 01JC4Z0000USUARIO0000000001
        createdAt:
          type: string
          example: '2026-07-23T10:00:00.000Z'
          format: date-time
        updatedAt:
          type: string
          example: '2026-07-23T10:00:00.000Z'
          format: date-time
      required:
        - id
        - projectId
        - provider
        - externalId
        - url
        - defaultBranch
        - visibility
        - origin
        - provisionedBy
        - createdAt
        - updatedAt
    ProvisionRepositoryDto:
      type: object
      properties:
        name:
          type: string
          example: checkout
          description: Имя репозитория у провайдера.
        visibility:
          type: string
          enum:
            - public
            - private
          example: private
        namespace:
          type: string
          example: acme
          description: >-
            Организация или группа. Если опущено, репозиторий создаётся в личной
            учётной записи того, кто зарегистрировал учётные данные.
      required:
        - name
        - visibility
    ProvisionRepositoryResponseDto:
      type: object
      properties:
        repository:
          $ref: '#/components/schemas/ProvisionedRepositoryResponseDto'
        bootstrap:
          description: >-
            Где остановился bootstrap Gitflow. Работа продолжается в фоне —
            следите через `GET /projects/:id/git/bootstrap`.
          allOf:
            - $ref: '#/components/schemas/PassoDeBootstrapResponseDto'
      required:
        - repository
        - bootstrap
    PsychologistAnalysisResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000ANALISE0000000001
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        sessionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
        tier:
          type: string
          enum:
            - leve
            - pesada
          example: leve
          description: >-
            Уровень сортировки. Два уровня используют ДЕЙСТВИТЕЛЬНО разные
            модели — именно поэтому реально расходится стоимость, а не только
            подпись.
        triggeredBy:
          type: string
          enum:
            - auto
            - manual
          example: auto
          description: '`manual` — повторный анализ, запрошенный человеком.'
        supersedes:
          type: object
          example: null
          nullable: true
          description: Id анализа, который заменяет этот.
        superseded:
          type: boolean
          example: false
        supersededAt:
          type: object
          example: null
          format: date-time
          nullable: true
        eventCountAtAnalysis:
          type: number
          example: 128
          description: Размер журнала на момент анализа — снимок, который он видел.
        createdAt:
          type: string
          example: '2026-07-26T18:00:00.000Z'
          format: date-time
        costMicros:
          type: number
          example: 4200
          description: >-
            Реальная стоимость в микро-USD, суммированная из `token_usage`
            раунда. Строка здесь есть только для ЗАВЕРШЁННОГО запуска: неудачный
            запуск её намеренно не записывает.
        hypothesisCount:
          type: number
          example: 3
          description: Сколько гипотез дал раунд.
      required:
        - id
        - projectId
        - sessionId
        - tier
        - triggeredBy
        - supersedes
        - superseded
        - supersededAt
        - eventCountAtAnalysis
        - createdAt
        - costMicros
        - hypothesisCount
    PsychologistContextBusinessRuleResponseDto:
      type: object
      properties:
        id:
          type: string
          example: RN-014
        title:
          type: string
          example: Cart accepts at most 50 items
      required:
        - id
        - title
    PsychologistContextResponseDto:
      type: object
      properties:
        alreadyAnalyzed:
          type: boolean
          example: false
          description: >-
            Для этой сессии уже существует ТЕКУЩИЙ анализ. Именно это делает
            автоматический путь идемпотентным: при `true` worker завершается
            сразу, ничего не тратя.
        sessionStatus:
          type: string
          enum:
            - created
            - active
            - closing
            - closed
            - closed_abnormally
          example: closed_abnormally
        terminationReason:
          type: object
          example: heartbeat_timeout
          nullable: true
        businessRules:
          type: array
          items:
            $ref: '#/components/schemas/PsychologistContextBusinessRuleResponseDto'
        priorHypotheses:
          description: Что уже было предложено раньше, чтобы раунд не повторялся.
          type: array
          items:
            $ref: '#/components/schemas/PriorHypothesisResponseDto'
      required:
        - alreadyAnalyzed
        - sessionStatus
        - terminationReason
        - businessRules
        - priorHypotheses
    PsychologistStatusResponseDto:
      type: object
      properties:
        enabled:
          type: boolean
          example: false
          description: >-
            Может ли сегодня выполниться НОВЫЙ раунд анализа, автоматический или
            по запросу. Отключение не затрагивает существующие данные — прошлые
            анализы и гипотезы остаются целыми и видимыми.
      required:
        - enabled
    QaEnvironmentResponseDto:
      type: object
      properties:
        sucesso:
          type: boolean
          description: '`false` when the broker refused; `motivo` explains.'
        status:
          type: string
          enum:
            - provisioning
            - running
            - failed
            - removed
        chave:
          type: string
          example: a1b2c3d4
          description: Instance key (8 chars of the task id).
        motivo:
          type: string
          nullable: true
      required:
        - sucesso
        - status
        - chave
        - motivo
    RagCoverageResponseDto:
      type: object
      properties:
        docs:
          $ref: '#/components/schemas/RagFileCoverageResponseDto'
        adr:
          $ref: '#/components/schemas/RagFileCoverageResponseDto'
        session:
          $ref: '#/components/schemas/RagSessionCoverageResponseDto'
        local:
          $ref: '#/components/schemas/RagLocalCoverageResponseDto'
        chunksTotal:
          type: number
        chunksWithoutVector:
          type: number
      required:
        - docs
        - adr
        - session
        - local
        - chunksTotal
        - chunksWithoutVector
    RagFeedbackInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
        searchId:
          type: string
          format: uuid
        chunkId:
          type: string
          format: uuid
        verdict:
          type: string
          enum:
            - util
            - irrelevante
        agent:
          type: string
          example: qa
          description: Slug агента — голосующий.
      required:
        - projectId
        - searchId
        - chunkId
        - verdict
        - agent
    RagFeedbackInternalResponseDto:
      type: object
      properties:
        searchId:
          type: string
          format: uuid
        chunkId:
          type: string
          format: uuid
        verdict:
          type: string
          enum:
            - util
            - irrelevante
        rank:
          type: number
          description: Позиция (с 1), которую оцениваемый фрагмент занимал в ТОМ поиске.
      required:
        - searchId
        - chunkId
        - verdict
        - rank
    RagFeedbackRequestDto:
      type: object
      properties:
        searchId:
          type: string
          format: uuid
          description: >-
            `searchId`, возвращённый поиском, давшим этот результат, — никогда
            не выдумывается клиентом.
        chunkId:
          type: string
          format: uuid
          description: >-
            Оцениваемый фрагмент. Должен быть одним из результатов, которые
            вернул ТОТ поиск, иначе 400.
        verdict:
          type: string
          enum:
            - util
            - irrelevante
          description: >-
            Два значения, а не шкала 1–5: более тонкая шкала порождает различия
            в мерке у разных голосующих, которые никакая агрегация потом не
            восстановит.
      required:
        - searchId
        - chunkId
        - verdict
    RagFeedbackResponseDto:
      type: object
      properties:
        searchId:
          type: string
          format: uuid
        chunkId:
          type: string
          format: uuid
        verdict:
          type: string
          enum:
            - util
            - irrelevante
        rank:
          type: number
          description: >-
            Позиция (с 1), которую оцениваемый фрагмент занимал в ТОМ поиске, —
            число, отделяющее «индекс плохой» от «веса неверны».
      required:
        - searchId
        - chunkId
        - verdict
        - rank
    RagFileCoverageResponseDto:
      type: object
      properties:
        filesInRepo:
          type: number
        filesIndexed:
          type: number
        truncated:
          type: boolean
      required:
        - filesInRepo
        - filesIndexed
        - truncated
    RagLocalCoverageResponseDto:
      type: object
      properties:
        filesIndexed:
          type: number
        folderName:
          type: string
          nullable: true
        lastAttachedAt:
          type: string
          nullable: true
          description: >-
            Реальный `MAX(chunks.created_at)` для этой области — никогда не
            угаданное «N минут назад» (RN-455).
      required:
        - filesIndexed
        - folderName
        - lastAttachedAt
    RagSearchInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
        query:
          type: string
          example: how the parallelism cap is decided
        topK:
          type: number
          example: 5
        sessionId:
          type: string
          format: uuid
          description: >-
            Сессия, в которой работает агент. Отсутствие означает, что у
            вызывающего не было сессии, — строка телеметрии всё равно
            записывается (RN-479), пропускается только повествование
            `rag.search` на временной шкале. Никогда не выдумывается api:
            сессия, которую она не получила, — сессия, которой не было.
        agent:
          type: string
          example: dev-lead
          description: >-
            Slug агента, записываемый как актор телеметрии (`actor_kind:
            agent`). Если отсутствует, используется актор `system`, а не
            выдумывается пользователь.
      required:
        - projectId
        - query
    RagSearchInternalHitResponseDto:
      type: object
      properties:
        chunkId:
          type: string
          format: uuid
          description: >-
            Id фрагмента — половина ссылки, нужной голосу `rag_feedback`
            (RN-480). Без него агент мог бы прочитать результат, но не имел бы
            способа сказать, что он полезен.
        path:
          type: string
          example: docs/adr/0080-chat-rag-pipeline-indexacao.md
          description: >-
            Путь к файлу (области `docs`/`adr`) или `session:<id>` (область
            `session`, без реального пути к файлу).
        chunk:
          type: string
          description: Полное содержимое найденного фрагмента.
        score:
          type: number
          description: >-
            Комбинированная оценка (вектор + лексика), уже отфильтрованная
            порогом.
        excerpt:
          type: string
          description: >-
            Короткий предпросмотр фрагмента, для показа без раздувания контекста
            модели.
      required:
        - chunkId
        - path
        - chunk
        - score
        - excerpt
    RagSearchInternalResponseDto:
      type: object
      properties:
        searchId:
          type: string
          nullable: true
          format: uuid
          description: >-
            Строка `rag_searches`, оставленная этим поиском (RN-479), — вторая
            половина ссылки для голоса. `null`, когда строка телеметрии не
            записана; тогда инструмент опускает id, а не предлагает модели
            ссылку, которая будет отклонена.
        hits:
          type: array
          items:
            $ref: '#/components/schemas/RagSearchInternalHitResponseDto'
        degraded:
          type: boolean
          description: >-
            `true`, когда эмбеддинг ЗАПРОСА был недоступен и поиск откатился к
            чисто лексическому (та же семантика, что `vectorAvailable: false` у
            `HybridSearchUseCase`).
      required:
        - searchId
        - hits
        - degraded
    RagSessionCoverageResponseDto:
      type: object
      properties:
        sessionsInProject:
          type: number
        sessionsIndexed:
          type: number
      required:
        - sessionsInProject
        - sessionsIndexed
    ReconhecerFalhaDeProtecaoResponseDto:
      type: object
      properties:
        status:
          type: object
          example: provisioned
          description: >-
            Поскольку `protect_branches` — ПОСЛЕДНИЙ шаг, подтверждение его сбоя
            закрывает bootstrap — проект становится доступным из дашборда.
      required:
        - status
    RecordDelegationInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        area:
          type: string
          example: qa
          description: >-
            Область, которая делегировала, — "qa" (Фаза 8b) или "infra" (Фаза
            8c). Текст, а не перечисление.
        taskId:
          type: string
          format: uuid
          example: 01JC4Z0000TASK00000000001
          description: >-
            Задача backlog за делегированием, если она есть. QA (Фаза 8b) всегда
            её отправляет; Infra (Фаза 8c) никогда — делегирование касается
            СЕССИИ, а не задачи.
        leadAgent:
          type: string
          example: qa-lead
        subagent:
          type: string
          example: qa-automacao
        status:
          type: string
          enum:
            - completed
            - failed
            - dispensed
          example: completed
        parecerArtifactId:
          type: string
          example: evt_01jc4z0000parecer000000001
          description: >-
            Id session_event `artifact.qa_verdict` для ВЕРДИКТА ЭТОЙ
            подспециальности — обязателен, когда `status` равен "completed".
        failureOrigin:
          type: string
          enum:
            - infra
            - modelo
            - codigo
            - politica
          example: infra
          description: >-
            ИСТОЧНИК сбоя (ADR 0020/0038), никогда методом исключения, —
            обязателен, когда `status` равен "failed".
        failureReason:
          type: string
          example: iteration limit reached without emit_perf_seguranca_verdict
        justification:
          type: string
          example: story with no relevant performance NFR
          description: >-
            Почему lead решил НЕ делегировать — обязательно, когда `status`
            равен "dispensed". Никогда не остаётся пустым: отказ от
            делегирования — записанное решение, а не молчание (CLAUDE.md, Фаза
            8b, пункт 2).
      required:
        - projectId
        - area
        - leadAgent
        - subagent
        - status
    RecordGateVerdictInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        taskId:
          type: string
          format: uuid
          example: 01JC4Z0000TAREFA00000000001
        gate:
          type: string
          enum:
            - qa
            - secops
          example: qa
        veredito:
          type: string
          enum:
            - approved
            - changes_requested
            - needs_user_decision
          example: changes_requested
          description: >-
            `needs_user_decision` (только QA, RN-860): цикл ждёт ответа
            пользователя на открытый вопрос — это не считается исправлением.
        resumo:
          type: string
          example: Missing tests for the insufficient-stock path.
        itens:
          example:
            - No test for a nonexistent SKU
          description: Что нужно изменить, пункт за пунктом.
          type: array
          items:
            type: string
        pergunta:
          type: string
          example: Should a nonexistent code answer 404?
          description: 'При `needs_user_decision`: новый открытый вопрос (до 500 символов).'
        sugestao:
          type: string
          example: 404 with a plain message.
          description: 'При `needs_user_decision`: предложение агента.'
        openQuestionId:
          type: string
          format: uuid
          description: >-
            При `needs_user_decision`: СУЩЕСТВУЮЩИЙ открытый вопрос вместо
            нового.
        maxCorrections:
          type: number
          example: 3
          description: >-
            Лимит циклов до того, как задача станет `blocked`. Если опущен,
            используется значение домена по умолчанию.
      required:
        - projectId
        - taskId
        - gate
        - veredito
        - resumo
        - itens
    RecordInfraGateVerdictInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        prActionId:
          type: string
          format: uuid
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRD
          description: Действие `open_infra_pr` (`proposed_action`), которое открыло PR.
        gate:
          type: string
          enum:
            - qa
            - secops
          example: secops
        veredito:
          type: string
          enum:
            - approved
            - changes_requested
          example: approved
        resumo:
          type: string
          example: Non-root image with no secret in ARG.
        itens:
          example: []
          type: array
          items:
            type: string
        maxCorrections:
          type: number
          example: 3
      required:
        - projectId
        - prActionId
        - gate
        - veredito
        - resumo
        - itens
    RecordProficiencyInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        windowFrom:
          type: string
          example: '2026-07-27T12:00:00.000Z'
          format: date-time
        windowTo:
          type: string
          example: '2026-07-27T12:15:00.000Z'
          format: date-time
        eventCount:
          type: number
          example: 240
        profiles:
          type: array
          items:
            $ref: '#/components/schemas/ProficiencyDraftDto'
      required:
        - projectId
        - windowFrom
        - windowTo
        - eventCount
        - profiles
    RecordProficiencyResponseDto:
      type: object
      properties:
        runId:
          type: string
          example: 01JC4Z0000RODADA00000000001
        profiles:
          type: array
          items:
            type: object
            additionalProperties: true
          description: Профили, записанные в этом раунде.
      required:
        - runId
        - profiles
    RecursosDoContainerResponseDto:
      type: object
      properties:
        cpus:
          type: number
          example: 2
          description: CPU, допускается дробное значение.
        memoryMb:
          type: number
          example: 4096
          description: Память в МиБ.
        pidsLimit:
          type: number
          example: 512
          description: >-
            Лимит процессов. Именно он сдерживает fork-бомбу, не завися от
            списка разрешённых глаголов.
      required:
        - cpus
        - memoryMb
        - pidsLimit
    RedeDoContainerResponseDto:
      type: object
      properties:
        ports:
          type: array
          items:
            $ref: '#/components/schemas/PortaDto'
        dns:
          type: array
          items:
            type: string
        extraHosts:
          type: array
          items:
            $ref: '#/components/schemas/HostExtraDto'
        version:
          type: number
          example: 1
      required:
        - ports
        - dns
        - extraHosts
        - version
    RegisterDto:
      type: object
      properties:
        email:
          type: string
          example: fulano@brabo.dev
          description: Email учётной записи.
        senha:
          type: string
          example: correct horse battery staple
          minLength: 12
          description: >-
            Пароль. Минимум 12 символов; требований к заглавным буквам, цифрам
            или символам нет.
        nome:
          type: string
          example: Fulano de Tal
      required:
        - email
        - senha
    RegisterGitCredentialDto:
      type: object
      properties:
        provider:
          type: string
          enum:
            - github
            - gitlab
          example: github
        token:
          type: string
          example: ghp_…
          maxLength: 512
          description: >-
            Personal access token. Шифруется envelope-шифрованием до того, как
            коснётся базы данных, и никогда не возвращается, даже зашифрованным.
            Регистрация НЕ проверяет токен (ADR 0050) — для проверки используйте
            `POST /users/me/credentials/{provider}/test`. Лимит в 512 символов —
            защита от абсурдного payload, а не проверка формата.
      required:
        - provider
        - token
    RegistroDeContainerResponseDto:
      type: object
      properties:
        status:
          type: string
          enum:
            - provisioning
            - running
            - stopped
            - failed
            - removed
          example: running
          description: То, что было ЗАПИСАНО (project_containers.status).
        imageVersion:
          type: number
          example: 1
        imagem:
          type: object
          nullable: true
          example: node:22-bookworm-slim
          description: >-
            Образ, ЗАМОРОЖЕННЫЙ на `imageVersion`, определённый из события
            `artifact.project_image` именно этой версии, — никогда не текущий,
            который мог быть пересмотрен с тех пор. `null`, когда событие этой
            версии не найдено.
        resources:
          $ref: '#/components/schemas/RecursosDoContainerResponseDto'
        failureReason:
          type: object
          nullable: true
          example: null
        createdAt:
          type: string
          format: date-time
        statusChangedAt:
          type: string
          format: date-time
      required:
        - status
        - imageVersion
        - imagem
        - resources
        - failureReason
        - createdAt
        - statusChangedAt
    ReindexProjectResponseDto:
      type: object
      properties:
        docs:
          $ref: '#/components/schemas/IndexDocsReportResponseDto'
        sessions:
          $ref: '#/components/schemas/ReindexSessionsResponseDto'
        embeddingAvailable:
          type: boolean
        embeddingReason:
          type: string
      required:
        - docs
        - sessions
        - embeddingAvailable
    ReindexSessionsResponseDto:
      type: object
      properties:
        total:
          type: number
        indexed:
          type: number
        chunksCreated:
          type: number
      required:
        - total
        - indexed
        - chunksCreated
    RenameSessionDto:
      type: object
      properties:
        name:
          type: object
          example: Cart checkout
          nullable: true
          maxLength: 80
          description: >-
            Новое имя. `null` (или пустая строка) ОЧИЩАЕТ имя, и сессия снова
            идентифицируется только хештегом, — это путь отмены, поэтому поле
            принимает null, а не требует маршрута удаления.
      required:
        - name
    RepoBootstrapStatusResponseDto:
      type: object
      properties:
        status:
          type: string
          enum:
            - provisioning
            - provisioned
            - provision_failed
            - awaiting_plan_decision
          example: provisioning
          nullable: true
          description: >-
            `null`, когда в этом проекте никогда не было создания.
            `awaiting_plan_decision` — ПОДКЛЮЧЁННЫЙ репозиторий со
            сгенерированным, но ещё не решённым планом: ничего не выполняется,
            пока кто-нибудь не одобрит план или не подключит как есть.
        sessionId:
          type: object
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
          nullable: true
          description: >-
            ВЫДЕЛЕННАЯ сессия bootstrap. Экран прогресса читает её события
            `bootstrap.step_*`, чтобы построить живой чек-лист.
        failedStep:
          type: string
          enum:
            - commit_pr_template
            - commit_branching_policy
            - commit_gitignore
            - create_dev_branch
            - create_qa_branch
            - create_rc_branch
            - protect_branches
          example: null
          nullable: true
        lastError:
          type: object
          example: null
          nullable: true
        attempts:
          type: number
          example: 1
          description: >-
            Сколько раз уже пытались выполнить bootstrap. Он идемпотентен и
            возобновляем: повтор не дублирует то, что уже сработало.
      required:
        - status
        - sessionId
        - failedStep
        - lastError
        - attempts
    ReportSessionTerminationDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        to:
          type: string
          enum:
            - closing
            - closed
            - closed_abnormally
          example: closed_abnormally
          description: >-
            `closing` НЕ терминальное: оно означает «я освобождаю эту сессию».
            Если её подхватит другая реплика, она остаётся живой; если никто не
            подхватит до таймаута, дренаж отправляет `closed_abnormally`.
            `created` и `active` исключены, потому что вход в них — решение api,
            а никогда не engine, сообщающего о завершении.
        reason:
          type: string
          example: heartbeat_timeout
          description: ИСТОЧНИК завершения, а не диагноз методом исключения.
      required:
        - projectId
        - to
    RequestManualHandoffDto:
      type: object
      properties:
        toAgent:
          type: string
          example: staff
          description: >-
            Slug целевого агента. Должен быть lead или агентом без области —
            никогда субагентом.
      required:
        - toAgent
    RequestModelPullDto:
      type: object
      properties:
        repoId:
          type: string
          example: meta-llama/Llama-3.1-8B-Instruct-GGUF
          description: '`<publisher>/<modelo>` в Hugging Face Hub.'
        estimatedSizeBytes:
          type: number
          example: 4900000000
          description: >-
            Приходит из поиска, когда Hub опубликовал размер, — никогда не
            оценивается наугад на клиенте.
      required:
        - repoId
    RequestPasswordResetDto:
      type: object
      properties:
        email:
          type: string
          example: fulano@brabo.dev
      required:
        - email
    ResetPasswordDto:
      type: object
      properties:
        token:
          type: string
          description: Одноразовый токен, полученный по email.
        novaSenha:
          type: string
          example: another pretty long passphrase
          minLength: 12
      required:
        - token
        - novaSenha
    ResolvedBindingResponseDto:
      type: object
      properties:
        modelId:
          type: string
          example: 01JC4Z0000MODELO00000000001
        origin:
          type: string
          enum:
            - workspace
            - project
            - area
            - agent
            - session
          example: project
          description: >-
            Из какой области действия пришло значение. Каскад: сессия → агент →
            проект → рабочее пространство, и раскрытие ИСТОЧНИКА позволяет
            интерфейсу сказать «унаследовано от проекта», а не показывать
            значение без происхождения.
        routingPreference:
          type: string
          enum:
            - price
            - throughput
            - latency
          nullable: true
          example: null
          description: >-
            Предпочтение маршрутизации того BINDING, КОТОРЫЙ ПОБЕДИЛ в каскаде
            (ADR 0166). Само по себе оно никогда не каскадируется: пропущенный
            уровень забирает своё предпочтение с собой, и ни один уровень не
            наследует только предпочтение другого.
        skipped:
          description: >-
            Более конкретные области действия, которые каскад отбросил до
            достижения `origin`. На обычном пути пусто.
          type: array
          items:
            $ref: '#/components/schemas/SkippedBindingResponseDto'
        herdadoDoLead:
          type: string
          example: secops
          description: >-
            Присутствует, когда у запрошенного агента не было собственной модели
            (ни у его области, ни у проекта) и он унаследовал РАЗРЕШЁННЫЙ
            binding lead своей области (RN-703). Тогда `origin` — это источник
            binding lead.
      required:
        - modelId
        - origin
        - routingPreference
        - skipped
    ResultadoPorProviderResponseDto:
      type: object
      properties:
        provider:
          type: string
          enum:
            - ollama
            - anthropic
            - openai
            - openrouter
            - nvidia-nim
            - together
            - deepinfra
            - bitdeer
            - vultr
            - 9router
            - opencode-zen
          example: openai
        descobertos:
          type: number
          example: 3
          description: >-
            Модели, которых не было в базе данных. Они входят НЕАКТИВНЫМИ —
            активация — это курация владельца (RN-043).
        reencontrados:
          type: number
          example: 1
          description: >-
            Модели, которые были `unavailable` и вернулись в каталог. Их
            `isActive` не затрагивается: выбор владельца переживает отсутствие.
        indisponibilizados:
          type: number
          example: 0
          description: >-
            Модели, исчезнувшие из удалённого каталога. Они становятся
            `unavailable` и НИКОГДА не удаляются — на них указывают binding и
            история затрат.
        pulado:
          type: string
          enum:
            - sem_capability
            - sem_credencial
            - falha
          description: >-
            Присутствует, когда провайдер не был синхронизирован. Пропущенный
            провайдер ничего не помечает недоступным: «я не знаю, что там» — не
            то же самое, что «там ничего нет».
        origemDaFalha:
          type: string
          enum:
            - infra
            - modelo
          description: >-
            Только при `pulado: "falha"`. Словарь источников из ADR 0020 —
            `infra`, когда до провайдера даже не дотянулись, `modelo`, когда он
            ответил отказом.
        detalhe:
          type: string
          example: openai responded with status 401
      required:
        - provider
        - descobertos
        - reencontrados
        - indisponibilizados
    ReturnStoryDto:
      type: object
      properties:
        reason:
          type: string
          example: The acceptance criteria don't cover the payment refusal case.
          description: >-
            Причина ОБЯЗАТЕЛЬНА: она становится сообщением, закреплённым в
            сессии PO, а возврат без причины возвращает работу без информации.
      required:
        - reason
    RollbackResponseDto:
      type: object
      properties:
        agent:
          type: string
          example: dev-api
        restoredFrom:
          type: number
          example: 2
          description: Версия, чьё содержимое восстановлено.
        toVersion:
          type: number
          example: 5
          description: >-
            НОВАЯ версия, созданная откатом. История неизменяема: восстановление
            ничего не удаляет, а добавляет.
        cacheInvalidated:
          type: boolean
          example: true
          description: Успешно ли инвалидирован кеш инструкций engine.
      required:
        - agent
        - restoredFrom
        - toVersion
        - cacheInvalidated
    RosterFactsResponseDto:
      type: object
      properties:
        executionActivated:
          type: boolean
          example: true
          description: >-
            В самой свежей сессии уже записан `execution.activated` — именно это
            приводит в состав dev agent по модулям.
        moduleNames:
          example:
            - api
            - web
          description: >-
            Имена модулей из ТЕКУЩЕГО module_map (наибольший `version`). Один
            dev agent на модуль после активации выполнения.
          type: array
          items:
            type: string
        gatesEverOpened:
          type: boolean
          example: true
          description: >-
            Какой-либо gate PR (dev или infra) открывался ХОТЯ БЫ РАЗ в этой
            сессии — именно это приводит в состав QA и SecOps. Охватывает всю
            сессию, а не окно последних N событий.
        delegatedSubagents:
          example:
            - qa-automacao
          description: >-
            Субагенты хотя бы с одним делегированием, записанным в сессии,
            независимо от исхода, — отказ — это записанное решение, а не
            молчание.
          type: array
          items:
            type: string
        activatedAgents:
          example:
            - arquiteto
            - infra
          description: >-
            Агенты хотя бы с одним `agent.activated` в сессии, сначала самые
            недавно активированные (по `seq`), одна запись на агента (RN-630).
            Охватывает ВСЮ сессию, а не окно в 200 событий: клиент прибавляет
            это к окну и доверяет этому, только когда `latestSessionId` —
            сессия, которую он читает.
          type: array
          items:
            type: string
        infraActive:
          type: boolean
          example: false
          description: В самой свежей сессии есть handoff в состоянии `accepted` к `infra`.
        uxDesignerActive:
          type: boolean
          example: false
          description: >-
            В самой свежей сессии есть handoff в состоянии `accepted` к
            `ux-designer` (ADR 0087).
        staffActive:
          type: boolean
          example: false
          description: >-
            В самой свежей сессии есть handoff в состоянии `accepted` к `staff`
            (docs/fluxo.yml, ADR 0088) — спящий для автоматического запуска,
            лишь отражает уже принятую РУЧНУЮ активацию.
      required:
        - executionActivated
        - moduleNames
        - gatesEverOpened
        - delegatedSubagents
        - activatedAgents
        - infraActive
        - uxDesignerActive
        - staffActive
    RoteamentoDeModuloInternalDto:
      type: object
      properties:
        modulo:
          type: string
          example: checkout-api
          description: Имя модуля — должно существовать в текущем module_map.
        imagemCandidata:
          type: string
          example: node:22-bookworm-slim
          description: >-
            Ссылка OCI с явным ТЕГОМ или digest для этого модуля. `latest`
            ОТКЛОНЯЕТСЯ с 400 — правило на уровне домена.
        porque:
          type: string
          example: >-
            This module is TypeScript on Node 22; the slim variant has the
            runtime and nothing else.
          description: Почему ЭТОТ образ для ЭТОГО модуля. Минимум 10 символов.
      required:
        - modulo
        - imagemCandidata
        - porque
    RoteamentoDeModuloResponseDto:
      type: object
      properties:
        modulo:
          type: string
          example: checkout-api
        imagemCandidata:
          type: string
          example: node:22-bookworm-slim
        porque:
          type: string
          example: >-
            This module is TypeScript on Node 22; the slim variant has the
            runtime and nothing else.
      required:
        - modulo
        - imagemCandidata
        - porque
    RoteamentoDeModulosGeradoResponseDto:
      type: object
      properties:
        roteamento:
          type: array
          items:
            $ref: '#/components/schemas/RoteamentoDeModuloResponseDto'
        version:
          type: number
          example: 1
      required:
        - roteamento
        - version
    RouteModulesToInfraInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
        roteamento:
          description: >-
            Один элемент на модуль текущего module_map. Образ-кандидат — это
            предложение Архитектора, а не решение: Infra выбирает среди
            кандидатов на более позднем шаге.
          type: array
          items:
            $ref: '#/components/schemas/RoteamentoDeModuloInternalDto'
      required:
        - projectId
        - roteamento
    RuleCoverageResponseDto:
      type: object
      properties:
        ruleId:
          type: string
          example: RN-014
        title:
          type: string
          example: Cart accepts at most 50 items
        coveredByStoryIds:
          example:
            - 01JC4Z0000HISTORIA000000001
          type: array
          items:
            type: string
        covered:
          type: boolean
          example: true
      required:
        - ruleId
        - title
        - coveredByStoryIds
        - covered
    RunInstructionsResponseDto:
      type: object
      properties:
        command:
          type: string
          example: cp .env.example .env && docker compose up --build
        urls:
          type: array
          items:
            $ref: '#/components/schemas/UrlDeExecucaoResponseDto'
        envExample:
          type: boolean
          example: true
          description: >-
            PR коммитит `.env.example`, который нужно скопировать перед
            запуском.
        app:
          description: >-
            AT-610 (ADR 0228): the command the container runs to start the app
            and the port it listens on. Absent when the Infra did not declare
            it.
          allOf:
            - $ref: '#/components/schemas/AppDeExecucaoResponseDto'
        qa:
          type: object
          description: >-
            AT-612 (ADR 0229): the infra services of the ephemeral QA instance
            declared by the Infra (`run.qa`). Absent when none.
        prUrl:
          type: string
          example: http://localhost:3000/git/acme/pulls/3
        version:
          type: number
          example: 1
        eventId:
          type: string
          example: 01JC4Z0000EVENTORUNINSTRUC01
        declaredAt:
          type: string
          example: '2026-10-11T12:00:00.000Z'
      required:
        - command
        - urls
        - envExample
        - prUrl
        - version
        - eventId
        - declaredAt
    RunLlmTurnDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        agentId:
          type: string
          example: dev-api
          description: Относит стоимость на этого агента в панели команды.
        messages:
          type: array
          items:
            type: object
            additionalProperties: true
          example:
            - role: user
              content: Read file X.
          description: История, уже собранная ContextManager engine.
        tools:
          type: array
          items:
            type: object
            additionalProperties: true
          description: Инструменты, предлагаемые модели; `parameters` — это JSON Schema.
        catalogoCompleto:
          type: boolean
          example: true
          description: >-
            Запрашивает ВЕСЬ каталог инструментов: маршрутизатор инструментов
            Jev (ADR 0179) для этого вызова пропускается. Engine устанавливает
            это, когда повторяет шаг, на котором ограниченное меню заставило
            модель ответить без вызова инструмента.
      required:
        - projectId
        - messages
    RunnerDeviceKeyListResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000CHAVE000000000001
        name:
          type: string
          example: laptop
        projectId:
          type: object
          example: 01JC4Z0000PROJETO000000001
          nullable: true
          description: >-
            Нулевое = МАШИННЫЙ ключ (ADR 0154): действует для любого проекта его
            владельца, а не только для этого. См. `especie`.
        especie:
          type: string
          enum:
            - projeto
            - maquina
          example: projeto
          description: >-
            `projeto` = привязан к проекту этого маршрута (ADR 0118). `maquina`
            = описывает МАШИНУ (ADR 0154) и появляется в списке каждого проекта,
            который она обслуживает, — его отзыв обрывает локального агента во
            всех них.
        createdAt:
          type: string
          example: '2026-08-27T12:00:00.000Z'
          format: date-time
        revokedAt:
          type: object
          example: null
          nullable: true
          description: Нулевое = активен. Отозванный продолжает появляться в списке.
        lastUsedAt:
          type: object
          example: null
          nullable: true
          description: >-
            Нулевое = ни разу не использовался — признак осиротевшего ключа
            (вкладка закрыта посреди потока автоматической настройки runner).
      required:
        - id
        - name
        - projectId
        - especie
        - createdAt
        - revokedAt
        - lastUsedAt
    RunnerProjectResponseDto:
      type: object
      properties:
        projectId:
          type: string
          example: 01JC4Z0000PROJETO000000001
        name:
          type: string
          example: Brabo
        workspaceDirName:
          type: string
          example: brabo-01jc4z
          description: >-
            Имя папки проекта (RN-109) — относительный сегмент под базой машины,
            никогда не абсолютный путь.
        workspaceVerifiedAt:
          type: object
          example: null
          nullable: true
          description: >-
            Когда runner впервые подтвердил папку (RN-423). Нулевое = никогда не
            подтверждалась. Это запись одного подтверждения, а не heartbeat
            (RN-468): оно не говорит, что папка доступна сейчас.
      required:
        - projectId
        - name
        - workspaceDirName
        - workspaceVerifiedAt
    RunnerTicketResponseDto:
      type: object
      properties:
        ticket:
          type: string
          description: >-
            Непрозрачный одноразовый токен, base64url. TTL 30 с: исчезает после
            первого успешного `connect/3` на сокете `/runner` или по истечении.
        expiresAt:
          type: string
          example: '2026-08-09T12:00:30.000Z'
          description: Когда истекает тикет (ISO 8601).
        engineWsUrl:
          type: string
          example: ws://localhost:4000/runner
          description: >-
            Публичный URL сокета Phoenix `/runner`, уже в ws(s):// — готов,
            чтобы клиент (CLI runner или веб) открыл соединение с `{ticket}` в
            параметрах.
      required:
        - ticket
        - expiresAt
        - engineWsUrl
    SemanticDuplicateCheckInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        kind:
          type: string
          enum:
            - business_rule
          example: business_rule
        title:
          type: string
          example: Greeting with the caller name
          description: >-
            Заголовок ТОЛЬКО ЧТО добавленного правила. Правила с тем же
            нормализованным заголовком (RN-080 делает его новым) исключаются из
            сравнения.
      required:
        - projectId
        - kind
        - title
    SemanticDuplicateCheckResponseDto:
      type: object
      properties:
        status:
          type: string
          enum:
            - warned
            - clean
            - skipped
            - nothing_to_compare
          description: >-
            `warned` НИКОГДА не блокирует: элемент уже записан. `skipped` несёт
            причину — нет провайдера эмбеддингов, демон недоступен, потолок 10 с
            — и также описывается в журнале событий.
        similarTo:
          $ref: '#/components/schemas/SemanticDuplicateSimilarDto'
        closest:
          nullable: true
          type: object
          allOf:
            - $ref: '#/components/schemas/SemanticDuplicateSimilarDto'
        similarity:
          type: number
          example: 0.83
        threshold:
          type: number
          example: 0.8
          description: Порог косинуса — ОТПРАВНАЯ ТОЧКА, не откалиброванная (ADR 0198).
        compared:
          type: number
          example: 12
          description: Сколько существующих элементов сравнено (самые свежие, до 100).
        total:
          type: number
          example: 12
          description: Сколько их в проекте.
        reason:
          type: string
          example: provider "ollama" did not answer
        message:
          type: string
          nullable: true
          description: >-
            Фраза, которую агент читает в результате инструмента; `null`, когда
            сказать нечего.
      required:
        - status
        - message
    SemanticDuplicateSimilarDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000HISTORIA000000001
        title:
          type: string
          example: Deterministic public greeting endpoint
        similarity:
          type: number
          example: 0.83
      required:
        - id
        - title
    SendAgentMessageDto:
      type: object
      properties:
        text:
          type: string
          example: I want to allow discount coupons at checkout.
          minLength: 1
          maxLength: 10000
          description: Сообщение пользователя активному агенту сессии.
      required:
        - text
    SendChatMessageDto:
      type: object
      properties:
        text:
          type: string
          example: Summarize what we have already decided about checkout.
          description: Сообщение пользователя. Ответ возвращается потоком SSE.
      required:
        - text
    SessaoResponseDto:
      type: object
      properties:
        accessToken:
          type: string
          description: Короткоживущий EdDSA JWT.
        expiresIn:
          type: number
          example: 900
          description: Секунды до истечения access-токена.
        locale:
          type: string
          example: pt-BR
          enum:
            - pt-BR
            - en
            - ru
            - zh-Hans
          description: >-
            Языковое предпочтение пользователя (основа i18n, Волна 6a). Оно
            приходит здесь — а не отдельным вызовом, — чтобы веб-приложению
            никогда не требовался лишний запрос только для того, чтобы узнать,
            на каком языке отображать экран.
      required:
        - accessToken
        - expiresIn
        - locale
    SessionAgentTokenUsageResponseDto:
      type: object
      properties:
        actorId:
          type: string
          example: dev-api
          description: Slug агента или id пользователя.
        costMicros:
          type: number
          example: 3400000
          description: Стоимость в микро-USD.
        inputTokens:
          type: number
          example: 128000
        outputTokens:
          type: number
          example: 14200
        lastCall:
          nullable: true
          description: >-
            The agent's latest call in the session (Jev excluded); null when
            none.
          type: object
          allOf:
            - $ref: '#/components/schemas/LastCallResponseDto'
      required:
        - actorId
        - costMicros
        - inputTokens
        - outputTokens
        - lastCall
    SessionContextSummaryResponseDto:
      type: object
      properties:
        sessoesLidas:
          description: >-
            Запрошенная сессия плюс сессия(и) выполнения, которые активировал её
            одобренный план Dev Lead (RN-828).
          type: array
          items:
            type: string
        regras:
          type: number
          example: 11
          description: '`artifact.business_rule` во всей сессии.'
        decisoes:
          type: number
          example: 6
          description: '`artifact.decision_record` во всей сессии.'
        itensDeRegras:
          description: >-
            Последние бизнес-правила сессии, не более 50; итог — `regras`
            (RN-844).
          type: array
          items:
            $ref: '#/components/schemas/ItemDeRegraResponseDto'
        itensDeDecisoes:
          description: >-
            Последние записи решений сессии, не более 50; итог — `decisoes`
            (RN-844).
          type: array
          items:
            $ref: '#/components/schemas/ItemDeDecisaoResponseDto'
        artefatosPorAgente:
          type: array
          items:
            $ref: '#/components/schemas/ArtefatosDoAgenteResponseDto'
        arquivosTocados:
          description: >-
            Файлы PR, открытых в этой сессии и в связанной сессии выполнения
            (прочитанные из diff PR, RN-844), затем файлы из payload
            `git_commit`/`git_push`.
          allOf:
            - $ref: '#/components/schemas/ArquivosTocadosResponseDto'
      required:
        - sessoesLidas
        - regras
        - decisoes
        - itensDeRegras
        - itensDeDecisoes
        - artefatosPorAgente
        - arquivosTocados
    SessionEventResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRB
          description: ULID события.
        sessionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
        seq:
          type: number
          example: 41
          description: Порядок внутри сессии. Монотонный и без пропусков.
        type:
          type: string
          example: chat.message
          description: Тип события. Полный перечень — в `docs/reference/events.md`.
        actor:
          $ref: '#/components/schemas/ActorResponseDto'
        payload:
          type: object
          additionalProperties: true
          example:
            role: assistant
            content: I'll open the epic first.
          description: Свободная форма, своя для каждого `type`. См. справочник событий.
        createdAt:
          type: string
          example: '2026-07-27T14:30:58.512Z'
          format: date-time
      required:
        - id
        - sessionId
        - seq
        - type
        - actor
        - payload
        - createdAt
    SessionListItemResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
          description: ULID сессии.
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        createdBy:
          type: string
          example: 01JC4Z0000USUARIO0000000001
          description: Кто открыл сессию.
        status:
          type: string
          enum:
            - created
            - active
            - closing
            - closed
            - closed_abnormally
          example: active
          description: >-
            Явная машина состояний: created → active → closing → closed |
            closed_abnormally. Недопустимый переход отвечает 409.
        kind:
          type: string
          enum:
            - consultiva
            - criativa
          example: criativa
          description: >-
            НАМЕРЕНИЕ, с которым открыта сессия, выбранное при создании и
            неизменяемое. `consultiva` (консультационная) — только беседа;
            `criativa` (творческая) производит и единственная входит в
            выполнение. Не путать с состоянием выполнения, которым остаётся
            событие `execution.activated` в журнале.
        name:
          type: object
          example: Cart checkout
          nullable: true
          description: >-
            Понятное имя или `null`. Экраны составляют его с хештегом id; оно
            никогда его не заменяет.
        nextSeq:
          type: number
          example: 42
          description: >-
            Следующий `seq` журнала событий. Служит курсором: `?afterSeq=41`
            получает всё после 41.
        createdAt:
          type: string
          example: '2026-07-27T14:03:22.187Z'
          format: date-time
        updatedAt:
          type: string
          example: '2026-07-27T14:31:09.004Z'
          format: date-time
        closedAt:
          type: object
          example: null
          format: date-time
          nullable: true
          description: Заполняется только в терминальных состояниях.
        terminationReason:
          type: object
          example: null
          nullable: true
          description: >-
            Причина, сообщённая engine при завершении (heartbeat_timeout,
            conversation_idle_timeout, killed, exception…). `null` при закрытии
            человеком или ещё живой сессии.
        traceParent:
          type: object
          example: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
          nullable: true
          description: >-
            W3C `traceparent` корневого span. Так вся сессия восстанавливается в
            Tempo.
        technical:
          type: boolean
          example: false
          description: >-
            Является ли это ТЕХНИЧЕСКОЙ сессией, открытой при создании
            репозитория (той, что связана из `repo_bootstraps`), в отличие от
            рабочей сессии. Это связь, а не имя: переименование сессии её не
            меняет. Экраны, выбирающие «самую свежую сессию», пропускают её,
            если она не единственная.
      required:
        - id
        - projectId
        - createdBy
        - status
        - kind
        - name
        - nextSeq
        - createdAt
        - updatedAt
        - closedAt
        - terminationReason
        - traceParent
        - technical
    SessionPendingWorkResponseDto:
      type: object
      properties:
        pending:
          type: boolean
          example: true
          description: >-
            Есть работа, мешающая закрытию из-за неактивности вкладки: handoff
            `offered`, действие `pending`, агент посреди хода, dev agent
            работает или заблокирован, или разговорный агент ждёт пользователя
            (RN-064, RN-581).
        motivo:
          type: object
          example: handoff po → arquiteto aguardando aceite
          nullable: true
          description: Что висит. `null`, когда ничего.
        aguardandoUsuarioDesde:
          type: string
          example: '2026-09-18T12:00:00.000Z'
          nullable: true
          description: >-
            Задаётся ТОЛЬКО когда единственное незавершённое — разговорный
            агент, ждущий пользователя (RN-581): момент, когда закончился его
            ход. Это единственный сигнал ожидания с потолком, и engine его
            применяет (`SESSION_CONVERSATION_IDLE_TIMEOUT_MS`, по умолчанию 8 ч)
            — после него сессия закрывается с `conversation_idle_timeout`. Иначе
            `null`.
      required:
        - pending
        - motivo
        - aguardandoUsuarioDesde
    SessionResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
          description: ULID сессии.
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        createdBy:
          type: string
          example: 01JC4Z0000USUARIO0000000001
          description: Кто открыл сессию.
        status:
          type: string
          enum:
            - created
            - active
            - closing
            - closed
            - closed_abnormally
          example: active
          description: >-
            Явная машина состояний: created → active → closing → closed |
            closed_abnormally. Недопустимый переход отвечает 409.
        kind:
          type: string
          enum:
            - consultiva
            - criativa
          example: criativa
          description: >-
            НАМЕРЕНИЕ, с которым открыта сессия, выбранное при создании и
            неизменяемое. `consultiva` (консультационная) — только беседа;
            `criativa` (творческая) производит и единственная входит в
            выполнение. Не путать с состоянием выполнения, которым остаётся
            событие `execution.activated` в журнале.
        name:
          type: object
          example: Cart checkout
          nullable: true
          description: >-
            Понятное имя или `null`. Экраны составляют его с хештегом id; оно
            никогда его не заменяет.
        nextSeq:
          type: number
          example: 42
          description: >-
            Следующий `seq` журнала событий. Служит курсором: `?afterSeq=41`
            получает всё после 41.
        createdAt:
          type: string
          example: '2026-07-27T14:03:22.187Z'
          format: date-time
        updatedAt:
          type: string
          example: '2026-07-27T14:31:09.004Z'
          format: date-time
        closedAt:
          type: object
          example: null
          format: date-time
          nullable: true
          description: Заполняется только в терминальных состояниях.
        terminationReason:
          type: object
          example: null
          nullable: true
          description: >-
            Причина, сообщённая engine при завершении (heartbeat_timeout,
            conversation_idle_timeout, killed, exception…). `null` при закрытии
            человеком или ещё живой сессии.
        traceParent:
          type: object
          example: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
          nullable: true
          description: >-
            W3C `traceparent` корневого span. Так вся сессия восстанавливается в
            Tempo.
      required:
        - id
        - projectId
        - createdBy
        - status
        - kind
        - name
        - nextSeq
        - createdAt
        - updatedAt
        - closedAt
        - terminationReason
        - traceParent
    SessionResponseLanguageResponseDto:
      type: object
      properties:
        language:
          type: string
          example: pt-BR
          description: Эффективный язык.
        origin:
          type: string
          enum:
            - sessao
            - conta
            - detectado
            - interface
          example: interface
          description: >-
            Откуда пришёл `language`: `sessao` (закреплён в этой сессии) >
            `conta` (выбор в Учётной записи) > `detectado` (обнаружен И
            подтверждён) > `interface` (язык интерфейса).
        sessionOverride:
          type: string
          nullable: true
          example: null
        account:
          type: string
          example: automatico
          description: '`automatico` или код, выбранный в Учётной записи.'
        detected:
          type: string
          nullable: true
          example: null
        interfaceLocale:
          type: string
          enum:
            - pt-BR
            - en
            - ru
            - zh-Hans
          example: pt-BR
        detectionQuestion:
          type: string
          nullable: true
          example: es
          description: >-
            Язык, на который указывают ВАШИ недавние сообщения, когда экран
            должен СПРОСИТЬ, использовать ли его для ответов (RN-624), — `null`
            означает, что вопроса нет. Обнаружение никогда само не меняет
            предпочтение: это делает только ответ `confirm` на `POST
            /users/me/preferences/detected-language`. Никогда не спрашивается,
            когда эффективный язык происходит из явного выбора (эта сессия или
            Учётная запись), когда он уже является обнаруженным или когда вы
            ранее отказались от этого языка. По мере возможности: сбой
            обнаружения — это `null`, никогда не ошибка этого маршрута.
      required:
        - language
        - origin
        - sessionOverride
        - account
        - detected
        - interfaceLocale
        - detectionQuestion
    SetAgentAutonomyDto:
      type: object
      properties:
        agentId:
          type: string
          example: dev-api
          description: Slug агента.
        actionType:
          type: string
          enum:
            - terminal
            - git_commit
            - git_push
            - pr_open
            - spend
            - git_repo_create
            - git_branch_create
            - git_branch_protect
            - write_file
            - open_adr_pr
            - git_merge
            - open_infra_pr
            - instruction_patch
            - parallelize
            - raise_max_parallel
            - propose_execution_plan
            - assess_implementability
            - container_start
            - container_stop
            - container_remove
            - container_recreate
            - container_start_via_runner
            - qa_environment_start
            - '*'
          example: terminal
          description: >-
            Тип действия или `"*"` для КАЖДОГО типа действия этого агента —
            «автоматический режим» ApprovalCard (RN-153). КОНКРЕТНОЕ правило
            всегда побеждает подстановочное: запись `terminal: deny` при уже
            включённом `"*": auto_approve` всё равно запрещает терминал.
        mode:
          type: string
          enum:
            - auto_approve
            - require_approval
            - deny
          example: auto_approve
          description: >-
            Автономия этого агента для этого типа. НЕ переопределяет
            `permissions.json`: шаблон в `deny` остаётся заблокированным,
            сколько бы автономии ни было у агента.
      required:
        - agentId
        - actionType
        - mode
    SetAreaBudgetDto:
      type: object
      properties:
        limitUsd:
          type: object
          example: 20
          minimum: 0
          nullable: true
          description: >-
            Лимит в ДОЛЛАРАХ для области — то же соглашение «доллары на входе,
            микро-USD на выходе», что и у бюджетов проекта/сессии
            (`BudgetsController`). `null` СНИМАЕТ лимит: область снова без
            ограничений, что также является значением по умолчанию для области,
            у которой лимит никогда не задавался.
      required:
        - limitUsd
    SetMaxParallelDto:
      type: object
      properties:
        maxParallel:
          type: number
          example: 4
          minimum: 1
          description: >-
            Сколько агентов lead области может иметь в сессии БЕЗ запроса вашего
            разрешения. Минимум 1 — ноль не означает «без ограничений», а
            означает недопустимую конфигурацию, и трактовка его как
            неограниченного превратила бы опечатку в неограниченные расходы.
      required:
        - maxParallel
    SetMirrorPathDto:
      type: object
      properties:
        mirrorPath:
          type: string
          nullable: true
          example: /home/you/mirrors/store
          description: >-
            Абсолютный путь НА МАШИНЕ ПОЛЬЗОВАТЕЛЯ, куда локальный агент
            копирует работу проекта (RN-515, ADR 0147). Отправьте `null` — ключ
            ОБЯЗАТЕЛЕН, его отсутствие даёт 400, — чтобы очистить назначение и
            выключить зеркало; `null` — нормальное состояние проекта, а не
            ошибка. Оно может быть только у проектов `mounted`/`runner`: в
            `container` источник — управляемый том на сервере, которого
            локальный агент не видит, и запрос отклоняется с 400 с указанием
            этой причины. API проверяет ТОЛЬКО ЛЕКСИЧЕСКУЮ форму (абсолютный,
            без `..`/`.`, никогда корень, системная папка или пересечение с
            собственным checkout Brabo) плюс оба направления петли
            источник↔назначение (назначение не может быть внутри `workspacePath`
            или содержать его) — он никогда не касается диска, потому что не
            видит машину, на которой будет находиться назначение, ровно как в
            режиме `runner` (RN-423). Разрешение символических ссылок — половина
            защиты на стороне локального агента, а не эта.
      required:
        - mirrorPath
    SetModelBindingDto:
      type: object
      properties:
        modelId:
          type: string
          format: uuid
          example: 9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b
          description: >-
            Модель для закрепления в этой области действия. Должна существовать
            в `GET /models`.
        routingPreference:
          type: string
          enum:
            - price
            - throughput
            - latency
          nullable: true
          example: throughput
          description: >-
            Как ХАБ выбирает upstream, обслуживающий модель (ADR 0166, RN-583).
            ОТСУТСТВИЕ сохраняет хранимое значение, если провайдер новой модели
            его принимает, и очищает его в противном случае; `null` очищает;
            значение для модели, провайдер которой не объявляет возможность
            `routingPreference` (`GET /llm/provider-capabilities`), отклоняется
            с 422. Оно идёт ВМЕСТЕ с этим binding: само по себе никогда не
            каскадируется.
      required:
        - modelId
    SetModelsActiveDto:
      type: object
      properties:
        modelIds:
          example:
            - 9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b
          description: Модели для включения или выключения. Весь пакет или ничего.
          type: array
          items:
            type: string
            format: uuid
        isActive:
          type: boolean
          example: true
          description: >-
            `true` заставляет модель появиться в селекторе и позволяет ей
            получить новый binding. Не затрагивает `availability`, — то, что
            синхронизация наблюдала у провайдера.
      required:
        - modelIds
        - isActive
    SetModelUsesDto:
      type: object
      properties:
        modelIds:
          example:
            - 9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b
          description: Модели для пометки. Весь пакет или ничего.
          type: array
          items:
            type: string
            format: uuid
        uses:
          type: array
          example:
            - codigo
            - analise
          description: >-
            ПОЛНЫЙ список применений — заменяет предыдущий, а не добавляет к
            нему. Пустой список снимает все пометки. Закрытый словарь:
            применение вне его приводит к сбою вызова, потому что свободный
            текст дал бы `code`, `coding` и `código` в одном фильтре.
          items:
            type: string
            enum:
              - codigo
              - documentacao
              - analise
              - imagem
              - conversa
      required:
        - modelIds
        - uses
    SetProjectPermissionsDto:
      type: object
      properties:
        allow:
          example:
            - Terminal(pnpm test:*)
            - Terminal(pnpm lint)
          description: Автоматически одобряемые шаблоны.
          type: array
          items:
            type: string
        deny:
          example:
            - Terminal(rm -rf *)
            - Terminal(git push --force*)
          description: Запрещённые шаблоны. `deny` побеждает `allow` и автономию агента.
          type: array
          items:
            type: string
        ask:
          example:
            - Terminal(pnpm add *)
          description: Шаблоны, всегда требующие решения человека.
          type: array
          items:
            type: string
      required:
        - allow
        - deny
        - ask
    SetSessionResponseLanguageDto:
      type: object
      properties:
        language:
          type: string
          nullable: true
          example: en
          maxLength: 35
          description: >-
            Язык, на котором агенты отвечают ВАМ, только в ЭТОЙ сессии (RN-618)
            — другие участники никогда не затрагиваются. Любой код BCP-47,
            распознаваемый сервером, хранимый в каноническом виде. Отправьте
            `null` (или `automatico`) — ключ ОБЯЗАТЕЛЕН, — чтобы вернуться к
            наследованию из вашей Учётной записи.
      required:
        - language
    SetToolRouterDto:
      type: object
      properties:
        enabled:
          type: boolean
          example: false
          description: >-
            Выбирает ли Jev инструмент для каждого шага агента. Включено по
            умолчанию; действует, только когда модель хода от OpenRouter.
      required:
        - enabled
    SkippedBindingResponseDto:
      type: object
      properties:
        scope:
          type: string
          enum:
            - workspace
            - project
            - area
            - agent
            - session
          example: agent
        modelId:
          type: string
          example: 01JC4Z0000MODELO00000000001
        reason:
          type: string
          enum:
            - unavailable
            - sem_tool_calling
          example: unavailable
          description: >-
            `unavailable`: модель исчезла из каталога провайдера.
            `sem_tool_calling`: запрос от агента, а модель только для чата —
            каскад перепроверяет возможность на каждом уровне, чтобы молча не
            нарушить RN-040.
      required:
        - scope
        - modelId
        - reason
    SocketTicketResponseDto:
      type: object
      properties:
        ticket:
          type: string
          description: >-
            Непрозрачный одноразовый токен base64url. TTL 30 с и однократное
            использование: исчезает после первого успешного `connect/3` или по
            истечении.
        expiresAt:
          type: string
          example: '2026-08-09T12:00:30.000Z'
          description: Когда истекает тикет (ISO 8601).
      required:
        - ticket
        - expiresAt
    SpendLinhaResponseDto:
      type: object
      properties:
        chave:
          type: string
          example: anthropic/claude-sonnet-4
          description: >-
            Ключ группировки: имя модели, имя провайдера, id проекта, id актора
            или id сессии, в зависимости от того, в каком списке появляется
            строка.
        rotulo:
          type: object
          example: Loja
          nullable: true
          description: >-
            Понятное имя, когда есть таблица с именем (проект). `null`, когда
            ключ уже является подписью.
        actorKind:
          type: object
          example: agent
          nullable: true
          description: >-
            Это заполняют только списки по акторам (`porAtor`, `porOwner`,
            `porAgente`); в остальных — `null`.
        costMicros:
          type: number
          example: 1250000
        assinaturaMicros:
          type: number
          example: 0
          description: >-
            Part of the cost from models curated as `subscription` (RN-954): a
            seat, shown apart from the US$ paid (`cost - subscription`).
        inputTokens:
          type: number
          example: 120000
        outputTokens:
          type: number
          example: 35000
        chamadas:
          type: number
          example: 42
      required:
        - chave
        - rotulo
        - actorKind
        - costMicros
        - assinaturaMicros
        - inputTokens
        - outputTokens
        - chamadas
    SpendPorDiaResponseDto:
      type: object
      properties:
        dia:
          type: string
          example: '2026-08-09'
          description: 'День в UTC. Ряд ПЛОТНЫЙ: день без расходов приходит как ноль.'
        costMicros:
          type: number
          example: 1250000
        chamadas:
          type: number
          example: 42
      required:
        - dia
        - costMicros
        - chamadas
    StoryComTarefasResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000HISTORIA000000001
        epicId:
          type: string
          example: 01JC4Z0000EPICO000000000001
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        sessionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
        title:
          type: string
          example: Add item to cart
        description:
          type: string
          example: As a buyer, I want to gather items before paying.
        rf:
          example:
            - The cart accepts up to 50 items
          description: Функциональные требования.
          type: array
          items:
            type: string
        rnf:
          example:
            - The response stays under 200 ms at p95
          description: Нефункциональные требования.
          type: array
          items:
            type: string
        businessRuleIds:
          example:
            - RN-014
          description: >-
            Покрытые бизнес-правила. Именно отсюда вычисляется покрытие
            правило→история в `GET /projects/:id/coverage`.
          type: array
          items:
            type: string
        dod:
          example:
            - Unit tests green
          description: Критерии готовности (Definition of done).
          type: array
          items:
            type: string
        dor:
          example:
            - Module defined
          description: Критерии готовности к работе (Definition of ready).
          type: array
          items:
            type: string
        moduleIds:
          example:
            - api
          description: >-
            Модули из `module_map`, которых касается история. История без модуля
            или с несуществующим модулем становится незакрытым вопросом
            архитектуры.
          type: array
          items:
            type: string
        status:
          type: string
          enum:
            - draft
            - ready
            - in_progress
            - done
          example: ready
        proposedReady:
          type: boolean
          example: true
          description: >-
            PO завершил историю, и она ждёт решения пользователя (Фаза 12c —
            RN-048). Сосуществует со `status: "draft"`: это предложение, а не
            состояние. Всегда `false` в проекте в режиме `auto`.
        returnedReason:
          type: object
          example: DoD too generic — spell out the acceptance criteria.
          nullable: true
          description: >-
            Почему пользователь вернул историю PO. `null`, когда её никогда не
            возвращали.
        returnedAt:
          type: object
          example: '2026-08-02T14:00:00.000Z'
          format: date-time
          nullable: true
        archivedAt:
          type: string
          example: null
          format: date-time
          description: >-
            Когда история была АРХИВИРОВАНА (RN-727). Архивные истории уходят из
            backlog, покрытия, плана и claim; `null` — история в игре.
          nullable: true
        archivedReason:
          type: string
          example: null
          description: Почему она была архивирована, если кто-то сказал (RN-727).
          nullable: true
        createdAt:
          type: string
          example: '2026-07-25T09:00:00.000Z'
          format: date-time
        updatedAt:
          type: string
          example: '2026-07-25T09:40:00.000Z'
          format: date-time
        tasks:
          type: array
          items:
            $ref: '#/components/schemas/TaskResponseDto'
      required:
        - id
        - epicId
        - projectId
        - sessionId
        - title
        - description
        - rf
        - rnf
        - businessRuleIds
        - dod
        - dor
        - moduleIds
        - status
        - proposedReady
        - returnedReason
        - returnedAt
        - archivedAt
        - archivedReason
        - createdAt
        - updatedAt
        - tasks
    StoryResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000HISTORIA000000001
        epicId:
          type: string
          example: 01JC4Z0000EPICO000000000001
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        sessionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
        title:
          type: string
          example: Add item to cart
        description:
          type: string
          example: As a buyer, I want to gather items before paying.
        rf:
          example:
            - The cart accepts up to 50 items
          description: Функциональные требования.
          type: array
          items:
            type: string
        rnf:
          example:
            - The response stays under 200 ms at p95
          description: Нефункциональные требования.
          type: array
          items:
            type: string
        businessRuleIds:
          example:
            - RN-014
          description: >-
            Покрытые бизнес-правила. Именно отсюда вычисляется покрытие
            правило→история в `GET /projects/:id/coverage`.
          type: array
          items:
            type: string
        dod:
          example:
            - Unit tests green
          description: Критерии готовности (Definition of done).
          type: array
          items:
            type: string
        dor:
          example:
            - Module defined
          description: Критерии готовности к работе (Definition of ready).
          type: array
          items:
            type: string
        moduleIds:
          example:
            - api
          description: >-
            Модули из `module_map`, которых касается история. История без модуля
            или с несуществующим модулем становится незакрытым вопросом
            архитектуры.
          type: array
          items:
            type: string
        status:
          type: string
          enum:
            - draft
            - ready
            - in_progress
            - done
          example: ready
        proposedReady:
          type: boolean
          example: true
          description: >-
            PO завершил историю, и она ждёт решения пользователя (Фаза 12c —
            RN-048). Сосуществует со `status: "draft"`: это предложение, а не
            состояние. Всегда `false` в проекте в режиме `auto`.
        returnedReason:
          type: object
          example: DoD too generic — spell out the acceptance criteria.
          nullable: true
          description: >-
            Почему пользователь вернул историю PO. `null`, когда её никогда не
            возвращали.
        returnedAt:
          type: object
          example: '2026-08-02T14:00:00.000Z'
          format: date-time
          nullable: true
        archivedAt:
          type: string
          example: null
          format: date-time
          description: >-
            Когда история была АРХИВИРОВАНА (RN-727). Архивные истории уходят из
            backlog, покрытия, плана и claim; `null` — история в игре.
          nullable: true
        archivedReason:
          type: string
          example: null
          description: Почему она была архивирована, если кто-то сказал (RN-727).
          nullable: true
        createdAt:
          type: string
          example: '2026-07-25T09:00:00.000Z'
          format: date-time
        updatedAt:
          type: string
          example: '2026-07-25T09:40:00.000Z'
          format: date-time
      required:
        - id
        - epicId
        - projectId
        - sessionId
        - title
        - description
        - rf
        - rnf
        - businessRuleIds
        - dod
        - dor
        - moduleIds
        - status
        - proposedReady
        - returnedReason
        - returnedAt
        - archivedAt
        - archivedReason
        - createdAt
        - updatedAt
    StreamLlmTurnDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        agentId:
          type: string
          example: criativo
        messages:
          type: array
          items:
            type: object
            additionalProperties: true
          example:
            - role: user
              content: Summarize what we decided.
        tools:
          type: array
          items:
            type: object
            additionalProperties: true
        catalogoCompleto:
          type: boolean
          example: true
          description: >-
            Запрашивает ВЕСЬ каталог инструментов: маршрутизатор инструментов
            Jev (ADR 0179) для этого вызова пропускается. Engine устанавливает
            это, когда повторяет шаг, на котором ограниченное меню заставило
            модель ответить без вызова инструмента.
      required:
        - projectId
        - messages
    SugestaoDoCatalogoResponseDto:
      type: object
      properties:
        cobranca:
          type: string
          enum:
            - api
            - subscription
            - free
          nullable: true
          description: 'Suggested billing band (9Router alias). Null: no suggestion.'
        preco:
          nullable: true
          description: Manufacturer list price; null when the table has no row.
          type: object
          allOf:
            - $ref: '#/components/schemas/PrecoSugeridoResponseDto'
      required:
        - cobranca
        - preco
    SweepQaEnvironmentsInternalDto:
      type: object
      properties:
        taskIdsEmVoo:
          description: >-
            Task ids that still have a gate in flight; every other active
            instance is removed.
          type: array
          items:
            type: string
        projectId:
          type: string
          format: uuid
          description: Limit the sweep to one project.
      required:
        - taskIdsEmVoo
    SweepQaEnvironmentsResponseDto:
      type: object
      properties:
        removidas:
          type: array
          items:
            type: string
        falhas:
          type: array
          items:
            type: object
            properties:
              taskId:
                type: string
              motivo:
                type: string
      required:
        - removidas
        - falhas
    SyncModelCatalogResponseDto:
      type: object
      properties:
        porProvider:
          type: array
          items:
            $ref: '#/components/schemas/ResultadoPorProviderResponseDto'
      required:
        - porProvider
    TarefaEmCursoDeDevResponseDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
        taskId:
          type: string
          format: uuid
        assignedTo:
          type: string
          example: dev-api
          description: Dev agent, который её удерживает.
      required:
        - projectId
        - taskId
        - assignedTo
    TaskResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000TAREFA00000000001
        storyId:
          type: string
          example: 01JC4Z0000HISTORIA000000001
        title:
          type: string
          example: POST /cart/items endpoint
        description:
          type: string
          example: Accepts SKU and quantity; validates stock.
        status:
          type: string
          enum:
            - todo
            - in_progress
            - in_review
            - done
          example: in_progress
        assignedTo:
          type: object
          example: dev-api
          nullable: true
          description: Slug dev agent, забравшего задачу.
        blocked:
          type: boolean
          example: false
          description: Задача заблокирована в ожидании решения или зависимости.
        blockedReason:
          type: object
          example: null
          nullable: true
        blockedOrigin:
          type: string
          enum:
            - infra
            - modelo
            - codigo
            - politica
          example: null
          nullable: true
          description: >-
            ИСТОЧНИК блокировки (ADR 0020/0038), если известен. `null` для
            каждой блокировки Фазы 4a — заполняет его только QA Lead (Фаза 8b).
        gateStatus:
          type: string
          enum:
            - awaiting_qa
            - awaiting_secops
            - awaiting_user
          example: null
          nullable: true
          description: >-
            Где находится PR задачи в конвейере gate: QA, затем SecOps, затем
            пользователь. `null`, пока PR не открыт.
        gateCorrectionCount:
          type: number
          example: 0
          description: Сколько раундов исправлений gate уже запросили.
        module:
          type: object
          example: api
          nullable: true
          description: >-
            Модуль, к которому относится задача, назначенный Dev Lead в
            одобренном плане выполнения (RN-678). Забирает её только dev agent
            этого модуля. `null`, пока ни один план его не назначил, — такую
            задачу можно взять, только когда у её истории ровно один модуль.
        createdAt:
          type: string
          example: '2026-07-25T10:00:00.000Z'
          format: date-time
        updatedAt:
          type: string
          example: '2026-07-25T12:30:00.000Z'
          format: date-time
      required:
        - id
        - storyId
        - title
        - description
        - status
        - assignedTo
        - blocked
        - blockedReason
        - blockedOrigin
        - gateStatus
        - gateCorrectionCount
        - module
        - createdAt
        - updatedAt
    TextDiffResponseDto:
      type: object
      properties:
        lines:
          type: array
          items:
            $ref: '#/components/schemas/DiffLineResponseDto'
        additions:
          type: number
          example: 3
        deletions:
          type: number
          example: 1
      required:
        - lines
        - additions
        - deletions
    ToolCallResponseDto:
      type: object
      properties:
        id:
          type: string
          example: call_01
        name:
          type: string
          example: read_file
        arguments:
          type: object
          additionalProperties: true
          example:
            path: src/index.ts
          description: Уже десериализовано; engine направляет к инструменту по `name`.
      required:
        - id
        - name
        - arguments
    ToolRoutingResponseDto:
      type: object
      properties:
        modelo:
          type: string
          example: typesafe/jev-1.13
        ofertadas:
          type: number
          example: 9
          description: Сколько инструментов было у агента на этом шаге.
        menuAntes:
          description: Имена инструментов ДО маршрутизатора.
          type: array
          items:
            type: string
        menuDepois:
          description: >-
            Имена инструментов, предложенных модели чата (весь каталог при любом
            откате).
          type: array
          items:
            type: string
        escolha:
          type: object
          nullable: true
          example: create_story
        confianca:
          type: object
          nullable: true
          example: 0.91
        segunda:
          type: object
          nullable: true
          additionalProperties: true
          example:
            opcao: create_task
            probabilidade: 0.06
        anterior:
          type: object
          nullable: true
          example: read_file
          description: Предыдущий инструмент того же запуска.
        aplicado:
          type: boolean
          description: Jev ответил И меню получилось меньше каталога.
        motivoDaQueda:
          type: string
          nullable: true
          enum:
            - timeout
            - erro_http
            - erro_de_rede
            - resposta_invalida
            - escolha_fora_das_opcoes
            - estado_grande
            - colisao_de_nome
        origemDaQueda:
          type: string
          nullable: true
          enum:
            - infra
            - modelo
            - codigo
        detalheDaQueda:
          type: object
          nullable: true
        latenciaMs:
          type: number
          example: 212
        custoMicros:
          type: number
          example: 52
          description: РЕАЛЬНАЯ стоимость ответа Jev (`usage.cost`), в микро-USD.
        gastoNaoRegistrado:
          type: boolean
          description: >-
            `true`, когда Jev взял плату, но строку `token_usage` записать не
            удалось.
      required:
        - modelo
        - ofertadas
        - menuAntes
        - menuDepois
        - escolha
        - confianca
        - segunda
        - anterior
        - aplicado
        - motivoDaQueda
        - origemDaQueda
        - detalheDaQueda
        - latenciaMs
        - custoMicros
        - gastoNaoRegistrado
    TransferOwnershipDto:
      type: object
      properties:
        userId:
          type: string
          format: uuid
          example: 3f1b2c8e-5a4d-4b7e-9c10-2d6f8a1b4c33
          description: >-
            Id пользователя, который становится титульным владельцем. Уже должен
            быть `owner` этого рабочего пространства.
      required:
        - userId
    TransitionSessionDto:
      type: object
      properties:
        status:
          type: string
          enum:
            - created
            - active
            - closing
            - closed
            - closed_abnormally
          example: closing
          description: >-
            Целевое состояние. Машина состояний отклоняет недопустимый переход с
            409 — из `created` можно перейти только в `active`, а из
            терминального состояния возврата нет.
      required:
        - status
    UnblockTaskInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        agentId:
          type: string
          example: dev-api
          description: Dev agent, чей временный сбой запланировал повтор.
      required:
        - projectId
        - agentId
    UnreadCursorDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        afterSeq:
          type: number
          example: 41
          minimum: 0
          description: >-
            Последний `seq`, который ЭТОТ браузер уже видел в этом проекте.
            Возвращает события со строго большим `seq`. `0` означает «ничего не
            видел» и приносит начало самой свежей сессии.
      required:
        - projectId
        - afterSeq
    UnreadEventsDto:
      type: object
      properties:
        cursors:
          maxItems: 200
          description: >-
            Один курсор на запрашиваемый проект. ПУСТОЙ список возвращает пустой
            список — «я ничего не просил» не значит «дай мне всё». Проект из
            другого рабочего пространства молча игнорируется, потому что курсор
            приходит из локального хранилища вызывающего и может нести остатки
            старого рабочего пространства.
          type: array
          items:
            $ref: '#/components/schemas/UnreadCursorDto'
      required:
        - cursors
    UpdateModelBillingDto:
      type: object
      properties:
        billing:
          type: string
          enum:
            - api
            - subscription
            - free
          example: subscription
      required:
        - billing
    UpdateModelPricingDto:
      type: object
      properties:
        inputPricePerMillionMicros:
          type: number
          example: 2500000
          description: Цена за миллион ВХОДНЫХ токенов, в микро-USD.
        outputPricePerMillionMicros:
          type: number
          example: 10000000
          description: Цена за миллион ВЫХОДНЫХ токенов, в микро-USD.
      required:
        - inputPricePerMillionMicros
        - outputPricePerMillionMicros
    UpdateProjectDto:
      type: object
      properties:
        name:
          type: string
          example: Checkout
          minLength: 2
        slug:
          type: string
          example: checkout
          pattern: ^[a-z0-9]+(-[a-z0-9]+)*$
          description: kebab-case. Уникален в пределах рабочего пространства.
        maxConsecutiveBlocked:
          type: number
          example: 3
          description: >-
            Предохранитель (Фаза 12b — RN-047): сколько последовательных задач,
            закончившихся блокировкой, останавливают dev agent модуля в
            idle_tripped. Если опущено, используется значение домена по
            умолчанию (DEFAULT_MAX_CONSECUTIVE_BLOCKED). Применяется со
            СЛЕДУЮЩЕЙ активации выполнения — не затрагивает уже работающих
            агентов.
        storyPromotion:
          type: string
          enum:
            - manual
            - auto
          example: auto
          description: >-
            Кто продвигает историю из `draft` в `ready` (Фаза 12c — RN-048).
            `auto` (по умолчанию для новых проектов начиная с RN-659): полная
            история сразу рождается `ready`, без шага человека. `manual`: PO
            предлагает, а ВЫ решаете во вкладке Backlog. Смена значения по
            умолчанию не переписала ни одного существующего проекта. Проверки
            домена (DoD/DoR/RF/правило/модули) ОДИНАКОВЫ в обоих режимах; режим
            меняет только то, кто запускает.
        language:
          type: string
          example: pt-BR
          maxLength: 35
          description: >-
            Язык ПРОЕКТА (RN-619) — его используют общие артефакты и ходы без
            автора-человека. Любой код BCP-47, распознаваемый сервером, хранимый
            в каноническом виде; нераспознанный — это 400. Если опущен, проект
            начинает с эффективного языка ответов своего создателя (RN-618:
            выбор в учётной записи > подтверждённое обнаружение > язык
            интерфейса).
    UpdateStoryInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        title:
          type: string
          example: Register a user
        description:
          type: string
          example: As a visitor, I want to sign up.
        questionId:
          type: string
          format: uuid
          description: >-
            RN-859 (ADR 0214): открытый вопрос, на который пользователь УЖЕ
            ответил, — только тогда можно исправить историю `ready` (без задачи
            в работе).
      required:
        - projectId
    UpdateStoryTitleDto:
      type: object
      properties:
        title:
          type: string
          example: Register a user
          description: Обрезанный по краям; от 1 до 200 символов.
      required:
        - title
    UpdateUserPreferencesDto:
      type: object
      properties:
        locale:
          type: string
          enum:
            - pt-BR
            - en
            - ru
            - zh-Hans
          example: en
          description: Язык интерфейса. Ограничен списком — не свободный BCP-47.
        responseLanguage:
          type: string
          example: es
          maxLength: 35
          description: >-
            Язык, на котором отвечают агенты (RN-618): `automatico` или ЛЮБОЙ
            код BCP-47, язык которого распознаёт сервер, — открытый список, в
            отличие от `locale`. Хранится в каноническом виде (`pt-br`
            становится `pt-BR`). Нераспознанный код — это 400, называющий
            правило. Никогда не меняет `locale`.
    UpdateWorkspaceDto:
      type: object
      properties:
        name:
          type: string
          example: Acme Corp
          minLength: 2
        slug:
          type: string
          example: acme-corp
          pattern: ^[a-z0-9]+(-[a-z0-9]+)*$
          description: kebab-case. Уникален в системе и виден в URL.
    UpsertBudgetDto:
      type: object
      properties:
        limitUsd:
          type: number
          example: 50
          minimum: 0
          description: >-
            Лимит в ДОЛЛАРАХ — единственное место в контракте, где говорится в
            долларах, потому что именно их вводит человек. Api переводит его в
            микро-USD, и ответы приходят в микро-USD.
        policy:
          type: string
          enum:
            - block
            - allow
          example: block
          description: >-
            `block` отклоняет вызов, когда он превысил бы лимит; `allow`
            пропускает его и только предупреждает.
      required:
        - limitUsd
        - policy
    UpsertCredentialDto:
      type: object
      properties:
        provider:
          type: string
          enum:
            - anthropic
            - openai
            - openrouter
            - nvidia-nim
            - together
            - deepinfra
            - bitdeer
            - vultr
            - 9router
            - opencode-zen
          example: anthropic
        apiKey:
          type: string
          example: sk-ant-api03-…
          maxLength: 512
          description: >-
            Ключ открытым текстом. Он шифруется envelope-шифрованием до того,
            как коснётся базы данных, и НИКОГДА не возвращается ни в одном
            ответе — даже зашифрованным. Лимит в 512 символов — защита от
            абсурдного payload, а не проверка формата: годен ли ключ, отвечает
            провайдер на `POST /users/me/credentials/{provider}/test`.
      required:
        - provider
        - apiKey
    UpsertPromptTemplateInternalDto:
      type: object
      properties:
        name:
          type: string
          example: dev-agent-kickoff
        version:
          type: string
          example: '3'
        body:
          type: string
          example: You are the dev agent for the {{modulo}} module...
        hash:
          type: string
          example: sha256:9f2c...
      required:
        - name
        - version
        - body
        - hash
    UrlDeExecucaoResponseDto:
      type: object
      properties:
        label:
          type: string
          example: Swagger
          maxLength: 60
        url:
          type: string
          example: http://localhost:8080/docs
        kind:
          type: string
          enum:
            - api_docs
            - web
          example: api_docs
      required:
        - label
        - url
        - kind
    UserCredentialResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000CREDENCIAL00000001
        provider:
          type: object
          example: anthropic
          description: Охватывает ключи LLM и токены git (`github`, `gitlab`).
        createdAt:
          type: string
          example: '2026-07-10T10:00:00.000Z'
          format: date-time
        updatedAt:
          type: string
          example: '2026-07-10T10:00:00.000Z'
          format: date-time
      required:
        - id
        - provider
        - createdAt
        - updatedAt
    UserPreferencesResponseDto:
      type: object
      properties:
        locale:
          type: string
          enum:
            - pt-BR
            - en
            - ru
            - zh-Hans
          example: pt-BR
        responseLanguage:
          type: string
          example: automatico
          description: >-
            `automatico` (с него начинается каждая учётная запись) или код
            BCP-47, явно выбранный на странице Учётной записи.
        detectedLanguage:
          type: string
          nullable: true
          example: null
          description: >-
            Язык, обнаруженный в ваших собственных сообщениях И подтверждённый
            вами, — никогда не неподтверждённое обнаружение. `null`, пока нет
            подтверждения.
        detectedLanguageConfirmedAt:
          type: string
          format: date-time
          nullable: true
        effectiveResponseLanguage:
          description: >-
            Что действует сегодня ВНЕ любой сессии и откуда это взялось: выбор в
            учётной записи > подтверждённое обнаружение > язык интерфейса.
          allOf:
            - $ref: '#/components/schemas/IdiomaEfetivoResponseDto'
      required:
        - locale
        - responseLanguage
        - detectedLanguage
        - detectedLanguageConfirmedAt
        - effectiveResponseLanguage
    VerifyEmailDto:
      type: object
      properties:
        token:
          type: string
          description: Одноразовый токен, полученный по email.
      required:
        - token
    WorkspaceComPapelResponseDto:
      type: object
      properties:
        workspace:
          $ref: '#/components/schemas/WorkspaceResponseDto'
        role:
          type: string
          enum:
            - viewer
            - developer
            - maintainer
            - owner
          example: maintainer
          description: Роль ВЫЗЫВАЮЩЕГО в этом рабочем пространстве.
      required:
        - workspace
        - role
    WorkspaceMemberComUsuarioResponseDto:
      type: object
      properties:
        userId:
          type: string
          example: 01JC4Z0000USUARIO0000000002
        role:
          type: string
          enum:
            - viewer
            - developer
            - maintainer
            - owner
          example: maintainer
          description: >-
            Роль РАБОЧЕГО ПРОСТРАНСТВА — то, что наследует каждый проект
            рабочего пространства, если строка проекта её не переопределяет
            (RN-471).
        name:
          type: string
          example: Senior Dev
          nullable: true
        email:
          type: string
          example: dev@brabo.dev
      required:
        - userId
        - role
        - name
        - email
    WorkspaceMemberResponseDto:
      type: object
      properties:
        workspaceId:
          type: string
          example: 01JC4Z0000WORKSPACE00000001
        userId:
          type: string
          example: 01JC4Z0000USUARIO0000000002
        role:
          type: string
          enum:
            - viewer
            - developer
            - maintainer
            - owner
          example: maintainer
          description: >-
            Линейная иерархия: owner > maintainer > developer > viewer. Каждая
            роль включает разрешения последующих.
        createdAt:
          type: string
          example: '2026-07-22T08:00:00.000Z'
          format: date-time
      required:
        - workspaceId
        - userId
        - role
        - createdAt
    WorkspaceResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000WORKSPACE00000001
        name:
          type: string
          example: Acme Corp
        slug:
          type: string
          example: acme-corp
          description: Уникален в системе; именно он появляется в URL.
        createdBy:
          type: string
          example: 01JC4Z0000USUARIO0000000001
        toolRouterEnabled:
          type: boolean
          example: true
          description: >-
            Выполняется ли шаг маршрутизации инструментов через Jev (ADR 0179)
            для этого рабочего пространства. Включено по умолчанию; действует,
            только когда модель хода от OpenRouter, поэтому `true` в рабочем
            пространстве без OpenRouter ничего не делает.
        createdAt:
          type: string
          example: '2026-07-20T09:12:00.000Z'
          format: date-time
        updatedAt:
          type: string
          example: '2026-07-20T09:12:00.000Z'
          format: date-time
      required:
        - id
        - name
        - slug
        - createdBy
        - toolRouterEnabled
        - createdAt
        - updatedAt
    WorkspaceSpendReportResponseDto:
      type: object
      properties:
        workspaceId:
          type: string
          example: 9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b
          format: uuid
        ownerId:
          type: string
          example: 9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b
          format: uuid
          description: Владелец ключей, профинансировавших этот расход (RN-058).
        dias:
          type: number
          example: 30
          description: Скользящее окно, в днях.
        projectId:
          type: object
          format: uuid
          nullable: true
          description: >-
            The `?projectId=` slice asked for (RN-952); `null` = whole
            workspace.
        totalMicros:
          type: number
          example: 1250000
        assinaturaMicros:
          type: number
          example: 0
          description: >-
            Part of the cost from models curated as `subscription` (RN-954): a
            seat, shown apart from the US$ paid (`cost - subscription`).
        inputTokens:
          type: number
          example: 120000
        outputTokens:
          type: number
          example: 35000
        chamadas:
          type: number
          example: 42
        porModelo:
          description: >-
            По МОДЕЛИ. Два провайдера, обслуживающие одно и то же имя, попадают
            в одну строку — их разделение вернуло бы ось учётных данных под
            другим именем.
          type: array
          items:
            $ref: '#/components/schemas/SpendLinhaResponseDto'
        porProvider:
          description: >-
            По ПРОВАЙДЕРУ (ADR 0076, RN-186). Эта ось говорит об УЧЁТНЫХ ДАННЫХ
            и поэтому существует только здесь, на маршруте, требующем `owner`
            (RN-060), — в ответе участника её нет, и это не фильтр, который её
            скрывает: её отклоняет тип репозитория при области действия актора
            (RN-187).
          type: array
          items:
            $ref: '#/components/schemas/SpendLinhaResponseDto'
        porProjeto:
          description: По ПРОЕКТУ внутри рабочего пространства.
          type: array
          items:
            $ref: '#/components/schemas/SpendLinhaResponseDto'
        porSessao:
          description: By SESSION (RN-952).
          type: array
          items:
            $ref: '#/components/schemas/SpendLinhaResponseDto'
        porAtor:
          description: >-
            По АКТОРУ — агент и человек в одном списке, различаемые по
            `actorKind`.
          type: array
          items:
            $ref: '#/components/schemas/SpendLinhaResponseDto'
        porOwner:
          description: >-
            Строки ЛЮДЕЙ (`actor_kind = "user"`) — блок, который handoff
            называет «По владельцу», потому что по RN-058 все они тратят ключ
            владельца. Кто владелец, указано в `ownerId`. Разбиение `porAtor`,
            без дополнительного запроса (RN-188).
          type: array
          items:
            $ref: '#/components/schemas/SpendLinhaResponseDto'
        porAgente:
          description: Строки АГЕНТОВ (`actor_kind = "agent"`). Вторая половина `porAtor`.
          type: array
          items:
            $ref: '#/components/schemas/SpendLinhaResponseDto'
        porDia:
          type: array
          items:
            $ref: '#/components/schemas/SpendPorDiaResponseDto'
      required:
        - workspaceId
        - ownerId
        - dias
        - projectId
        - totalMicros
        - assinaturaMicros
        - inputTokens
        - outputTokens
        - chamadas
        - porModelo
        - porProvider
        - porProjeto
        - porSessao
        - porAtor
        - porOwner
        - porAgente
        - porDia
    WorkspaceSummaryResponseDto:
      type: object
      properties:
        activeProjects:
          type: number
          example: 4
          description: >-
            Число проектов в рабочем пространстве. В домене нет флага «активный»
            — учитывается каждый проект.
        agentCount:
          type: number
          example: 6
          description: >-
            Различные агенты (actorKind=agent), потратившие токены в этом
            месяце, суммарно по всем проектам рабочего пространства, — включая
            подспециальности областей (Фаза 8).
        spentMicros:
          type: number
          example: 12500000
          description: >-
            Расходы текущего месяца в микро-USD, суммированные по
            token_usage.createdAt.
      required:
        - activeProjects
        - agentCount
        - spentMicros
