openapi: 3.0.0
paths:
  /.well-known/jwks.json:
    get:
      description: >-
        Uma chave em operação normal; duas durante uma rotação de
        AUTH_JWT_SECRET.
      operationId: JwksController_jwks
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JwksResponseDto'
      summary: Chaves públicas de verificação do access token
      tags:
        - auth
  /auth/login:
    post:
      description: >-
        E-mail inexistente, senha errada e conta bloqueada devolvem exatamente a
        mesma resposta 401 — mesmo corpo, mesmo status.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Credenciais inválidas.
        '403':
          description: E-mail ainda não verificado.
      summary: Autentica e emite o par access + refresh
      tags:
        - auth
  /auth/logout:
    post:
      description: >-
        Sempre 204, mesmo para token desconhecido — responder 401 aqui seria um
        oráculo de validade de token.
      operationId: AuthController_logout_
      parameters: []
      responses:
        '204':
          description: Sessão encerrada. Os cookies da sessão são limpos.
      summary: Revoga a família do refresh apresentado
      tags:
        - auth
  /auth/oauth/{provider}/callback:
    get:
      description: >-
        Sucesso vai para `WEB_ORIGIN/` já com os cookies da sessão gravados;
        falha vai para `WEB_ORIGIN/login?oauth_error=1`, sem detalhar o motivo.
      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: Redireciona para a web.
          headers:
            Location:
              description: Destino na web.
              schema:
                type: string
                example: http://localhost:5173/
      summary: Recebe o callback OAuth do login social
      tags:
        - auth
  /auth/oauth/{provider}/start:
    get:
      description: >-
        O `state` vai assinado por HMAC com propósito PRÓPRIO — nunca o do fluxo
        de conexão de git ao projeto (ver ADR 0084).
      operationId: AuthController_oauthStart
      parameters:
        - name: provider
          required: true
          in: path
          schema:
            enum:
              - github
              - gitlab
            type: string
      responses:
        '302':
          description: Redireciona para o provider.
          headers:
            Location:
              description: A URL de autorização do provider.
              schema:
                type: string
                example: https://github.com/login/oauth/authorize?…
        '400':
          description: Provider fora de `github`/`gitlab`.
      summary: Redireciona para o provider OAuth do login social
      tags:
        - auth
  /auth/refresh:
    post:
      description: >-
        O refresh apresentado é consumido. Reapresentar um token já rotacionado
        revoga a família INTEIRA e grava um evento de segurança — mesmo quando a
        causa foi um duplo envio do cliente.
      operationId: AuthController_refresh_
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessaoResponseDto'
        '401':
          description: Refresh inválido, expirado ou já usado.
      summary: Rotaciona o refresh e emite um par novo
      tags:
        - auth
  /auth/register:
    post:
      description: >-
        Responde 202 tanto para endereço novo quanto para um já cadastrado — a
        resposta não revela se a conta existe. No segundo caso nada é criado e o
        dono do endereço recebe um aviso.
      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: Senha não cumpre a política mínima.
        '403':
          description: Cadastro fechado (AUTH_REGISTRATION_ENABLED=false).
      summary: Cria uma conta e envia o e-mail de verificação
      tags:
        - auth
  /auth/request-password-reset:
    post:
      description: >-
        Responde 202 tanto para endereço conhecido quanto desconhecido. É também
        o caminho de quem foi importado do Keycloak e ainda não definiu senha.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
      summary: Pede o link de redefinição de senha
      tags:
        - auth
  /auth/reset-password:
    post:
      description: >-
        Revoga TODAS as sessões do usuário. Não emite tokens: quem redefine a
        senha é mandado ao login, para que comprometer o e-mail não equivalha a
        tomar a conta num passo só.
      operationId: AuthController_resetPassword
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ResetPasswordDto'
      responses:
        '204':
          description: >-
            Senha definida. TODAS as sessões vivas do usuário são revogadas
            junto.
        '400':
          description: Link inválido/expirado, ou senha não cumpre a política.
      summary: Define a senha nova a partir do token
      tags:
        - auth
  /auth/verify-email:
    post:
      operationId: AuthController_verifyEmail
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VerifyEmailDto'
      responses:
        '204':
          description: E-mail verificado; o login já funciona.
        '400':
          description: Link inválido, expirado ou já usado — os três com a mesma resposta.
      summary: Confirma o e-mail com o token de uso único
      tags:
        - auth
  /gates:
    get:
      description: >-
        Índice declarativo dos gates do fluxo (ADR 0054). A tela deriva daqui
        quais etapas existem e quem julga cada uma, em vez de repetir a lista no
        código — que era como ela envelhecia sem ninguém notar.
      operationId: GatesController_listar
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GatesResponseDto'
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: O registro de gates ativos
      tags:
        - gates
  /git/oauth/{provider}/callback:
    get:
      description: >-
        Pública porque quem chega é o NAVEGADOR do usuário vindo do provider,
        sem sessão da api. Não é irrestrita: o `state` é verificado por HMAC e
        sem um válido a chamada é recusada. Nunca responde JSON — sempre
        redireciona, porque um corpo de erro cru numa navegação do navegador
        seria uma experiência péssima.
      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: >-
            Sucesso vai para `WEB_ORIGIN/projects/:id?git=connected`; falha vai
            para `WEB_ORIGIN/git-error`. O erro NÃO vaza o motivo na URL.
          headers:
            Location:
              description: Destino na web.
              schema:
                type: string
                example: http://localhost:5173/projects/01JC…?git=connected
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não existe.
      summary: Recebe o retorno OAuth e redireciona para a web
      tags:
        - git
  /health:
    get:
      description: >-
        Executa um `select 1`. Revela só se o banco responde; nem topologia nem
        versão. Pública porque quem chama é o kubelet, que não carrega token.
      operationId: HealthController_check
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthStatusResponseDto'
        '503':
          description: O banco não respondeu. O corpo traz a mensagem em `details`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthStatusResponseDto'
      summary: Readiness — o processo consegue de fato servir tráfego
      tags:
        - infrastructure
  /internal/first-account:
    post:
      description: >-
        Para o instalador de uma linha, que roda onde nenhum transporte de
        e-mail está configurado: o `.env` gerado não traz variável de e-mail,
        `MAIL_TRANSPORT` cai em `log`, e o cadastro normal espera um e-mail que
        nunca chega. A conta nasce verificada porque quem roda o instalador já
        provou algo MAIS FORTE que controlar uma caixa de e-mail — controla a
        máquina, o `.env` e o daemon do Docker. O cadastro normal não muda.
        Recusa com 409 quando a instalação tem QUALQUER usuário: a condição é
        sobre a instalação, não sobre este e-mail, então a restauração de uma
        migração silencia o passo pelo mesmo teste. A senha nunca é gerada aqui
        e nunca é guardada em lugar nenhum senão como hash argon2id; o workspace
        pessoal (RN-410) nasce na mesma transação.
      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: >-
            E-mail malformado, ou senha que a política do DOMÍNIO recusa — o
            mesmo código que o cadastro chama, nunca uma segunda régua.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '409':
          description: A instalação já tem usuário. Nada foi criado.
      security:
        - service-token: []
      summary: Cria a PRIMEIRA conta de uma instalação, já verificada
      tags:
        - internal
  /internal/gates:
    get:
      description: >-
        O registro DESCREVE, não executa: nenhum gate passa a ser aplicado por
        causa dele, e mudar um campo aqui não muda comportamento nenhum. Cada
        gate diz onde mora a prova de que passou (`evidencia`), porque nem toda
        prova está no event log.
      operationId: InternalGatesController_ler
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GateRegistryResponseDto'
        '403':
          description: Service token ausente ou diferente do compartilhado.
      security:
        - service-token: []
      summary: Lê o registro declarativo de gates
      tags:
        - internal
  /internal/graph/prompt-templates:
    post:
      description: >-
        Se já existe uma versão com o MESMO hash para o mesmo `name`, nenhuma
        versão nova é criada — a existente é devolvida (o acerto de idempotência
        não muda o status HTTP: sempre 201, por simplicidade — o corpo é
        idêntico ao que teria sido criado).
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '503':
          description: >-
            Neo4j não configurado ou inalcançável — não há fallback possível
            para ler/gravar um template.
      security:
        - service-token: []
      summary: Publica uma versão de prompt template, idempotente por hash
      tags:
        - internal
  /internal/graph/prompt-templates/{name}:
    get:
      description: >-
        Uma `version` específica busca por igualdade; omitida, busca a versão
        `active` mais recente. 404 se o template (ou a versão pedida) não
        existe.
      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: Service token ausente ou diferente do compartilhado.
        '404':
          description: Template não existe, ou não tem a versão pedida.
        '503':
          description: >-
            Neo4j não configurado ou inalcançável — não há fallback possível
            para ler/gravar um template.
      security:
        - service-token: []
      summary: Busca um prompt template pelo nome
      tags:
        - internal
  /internal/machine-device-keys:
    post:
      description: >-
        Para o instalador de uma linha, logo depois de criar a primeira conta: o
        agente local precisa de uma credencial, e a única que existia era presa
        a um PROJETO — numa instalação que ainda não tem nenhum. O par é gerado
        NA MÁQUINA e só a metade pública chega aqui; grave o `id` devolvido na
        JWK privada como `kid` (RN-475), que é o único vínculo entre o arquivo
        em disco e a metade pública no servidor. Não há `userId` no corpo DE
        PROPÓSITO: o dono é o ÚNICO usuário da instalação, resolvido pela api,
        então ter o service token nunca significa escolher de quem é a
        credencial cunhada. Registrar SUBSTITUI: as chaves de máquina ativas do
        dono são revogadas na mesma transação, então uma máquina reinstalada
        funciona e mil chaves de máquina não podem existir. Chaves de projeto
        (ADR 0118) nunca são tocadas.
      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: >-
            A JWK não é uma chave PÚBLICA Ed25519 — JSON malformado, `kty`/`crv`
            errados, `x` ausente, ou trazendo `d` (essa é a metade privada, e é
            recusada pelo nome em vez de guardada).
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '409':
          description: >-
            A instalação não tem usuário, ou tem mais de um. Nada foi gravado.
            Esta rota é do momento da instalação e se cala de vez quando a
            instalação tem um time.
      security:
        - service-token: []
      summary: Registra a chave de dispositivo de MÁQUINA de uma instalação nova
      tags:
        - internal
  /internal/models/sync:
    post:
      description: >-
        Nunca falha por causa de um provider: cada um responde por si no
        relatório, com o motivo de ter sido pulado e a origem da falha quando
        houve. Provider que não respondeu é PULADO, não marcado indisponível.
      operationId: InternalModelsController_sync
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SyncModelCatalogResponseDto'
        '403':
          description: Service token ausente ou diferente do compartilhado.
      security:
        - service-token: []
      summary: Sincroniza o catálogo de modelos de todos os providers
      tags:
        - internal
  /internal/projects/{projectId}/backlog:
    get:
      description: >-
        A MESMA árvore épico → história → tarefa da aba Backlog, pelo mesmo caso
        de uso (três leituras por projeto, nunca N+1). É com ela que o PO vê um
        épico órfão e uma história sem tarefa em vez de recriar o que já existe
        (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: Service token ausente ou diferente do compartilhado.
      security:
        - service-token: []
      summary: O backlog do projeto em árvore, para o PO ler o que já foi escrito
      tags:
        - internal
  /internal/projects/{projectId}/business-rules:
    get:
      description: >-
        Todo `artifact.business_rule` das sessões do projeto — não só o da
        sessão corrente, que era o teto do contexto de kickoff do PO — com a
        `description` inteira e quais histórias já citam cada regra.
        `uncoveredCount` é a pendência: regra que nenhuma história cobre
        (RN-164). Projeto sem regra nenhuma responde `200` com lista vazia:
        "nenhuma regra capturada ainda" não é erro.
      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: Service token ausente ou diferente do compartilhado.
      security:
        - service-token: []
      summary: As regras de negócio do projeto, com cobertura, para o PO ler
      tags:
        - internal
  /internal/projects/{projectId}/container-exec:
    post:
      description: >-
        Chamada só pelo engine, quando `Engine.Actions.TerminalExecutor` decidiu
        que o comando cabe dentro do container real (`execution_mode: container`
        com uma linha `running` REGISTRADA). Faz proxy para
        `ContainerBrokerPort.exec` (ADR 0130/0134) — a última das cinco
        operações do broker a ganhar chamador real. `sucesso: false` na resposta
        — nunca um erro lançado — é a forma NORMAL de broker que recusou ou é
        inalcançável: o container pode ter morrido ou sido removido por fora
        entre a linha `running` registrada e esta chamada (RN-486, registrado e
        observado nunca se fundem), e isso é falha do comando, não erro de
        transporte.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '403':
          description: Service token ausente ou diferente do compartilhado.
      security:
        - service-token: []
      summary: >-
        Executa um comando de terminal DENTRO do container real do projeto
        (RN-492)
      tags:
        - internal
  /internal/projects/{projectId}/container-spec:
    get:
      description: >-
        Identidade do projeto, modo de execução e a decisão de imagem vigente do
        Arquiteto. O broker revalida tudo antes de entregar qualquer coisa ao
        daemon — ler daqui não é o mesmo que confiar, e a recusa nomeia o campo.
        Nenhum caminho ABSOLUTO é devolvido: a origem do bind é resolvida pelo
        daemon contra o sistema de arquivos do HOST, e um caminho de dentro do
        container da api montaria em silêncio uma pasta vazia. O que viaja é
        `localizacao` (RN-503) — qual das duas raízes do broker usar, mais o
        segmento relativo que a raiz não cobre.
      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: Service token ausente ou diferente do compartilhado.
        '404':
          description: Projeto não encontrado.
      security:
        - service-token: []
      summary: O que o broker precisa para compor ele mesmo a spec do container
      tags:
        - internal
  /internal/projects/{projectId}/git-remote:
    get:
      description: >-
        Devolve a origem limpa (sem credencial embutida) e, para provider
        remoto, o token do DONO do workspace (RN-058) decifrado na hora. Quem
        consome injeta o token por invocação e NUNCA o grava em arquivo —
        `.git/config` fica dentro da pasta onde a RN-075 concede ao dev agent
        leitura auto-aprovada.
      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: Service token ausente ou diferente do compartilhado.
        '404':
          description: >-
            Projeto sem repositório provisionado, ou o dono do workspace não tem
            credencial registrada para o provider do repositório.
      security:
        - service-token: []
      summary: O remoto de trabalho do projeto, para fetch e push
      tags:
        - internal
  /internal/projects/{projectId}/mirror-sync-result:
    post:
      description: >-
        Chamada só pelo engine, depois que o runner empurra `mirror_sync_result`
        pelo canal — o MESMO caminho do `workspace_confirm` (runner → canal →
        engine → api), nunca um segundo mecanismo: o engine relata, não grava a
        tabela. Isto é TELEMETRIA, não evento de domínio: nunca `session_events`
        (rodada de espelho não tem sessão e `session_id` é `NOT NULL`, o mesmo
        raciocínio que fez de `rag_searches` uma tabela) e nunca
        `proposed_action` (a escrita do espelho é configuração que o usuário
        declarou, não agente pedindo para agir). `ok` é o que decide o desfecho
        — nunca a presença de uma contagem ou de uma mensagem: rodada que copiou
        0 arquivos é normal e não pode ser indistinguível de uma que nunca
        rodou. Gravar nunca derruba o que mede: a cópia já está feita quando
        isto é chamado, e o engine só registra em log uma recusa aqui. Projeto
        cujo destino foi limpo nesse meio-tempo AINDA grava — a rodada
        aconteceu, e o erro dela costuma ser o que explica o que deu errado.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Projeto não existe.
      security:
        - service-token: []
      summary: O agente local relata o desfecho de uma rodada de espelho (RN-517)
      tags:
        - internal
  /internal/projects/{projectId}/module-contracts:
    get:
      description: >-
        Uma entrada por módulo do module_map VIGENTE: o que ele consome
        (`dependeDe`, o `dependsOn` do mapa) e o que expõe (`expoe`, do
        `artifact.module_contracts` vigente do Arquiteto, ou `null` quando
        nenhum foi declarado). É o que um dev agent lê em vez de abrir o
        worktree de outro módulo (ADR 0200). Projeto sem contrato responde `200`
        com `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: Service token ausente ou diferente do compartilhado.
      security:
        - service-token: []
      summary: Os contratos entre módulos, para um dev agent ler (RN-684)
      tags:
        - internal
  /internal/projects/{projectId}/product-metrics:
    get:
      description: >-
        O MESMO relatório do script `analise:funil` (ADR 0089) — funil sessão →
        commit → PR → merge, lead time real e frequência de deploy real — pelas
        mesmas funções puras e a mesma consulta
        (`apps/api/src/application/services/funil-metrics.ts`), para que os dois
        nunca divirjam sobre o mesmo fato. Fecha `docs/fluxo.yml` (papel `po`,
        entrada `metricas-de-produto`, antes `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: Service token ausente ou diferente do compartilhado.
        '404':
          description: Projeto não existe.
      security:
        - service-token: []
      summary: O funil de entrega do projeto e métricas DORA parciais, para o PO ler
      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: Service token ausente ou diferente do compartilhado.
        '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: Service token ausente ou diferente do compartilhado.
        '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: >-
        Chamada só pelo engine, depois que um runner conecta e manda
        `workspace_confirm` pelo canal. O runner é a FONTE DE VERDADE do caminho
        — a api sobrescreve `workspacePath` com o que ele relatou, depois de
        revalidá-lo lexicamente (raiz de sistema/sobreposição com o Brabo seguem
        proibidas mesmo vindo do runner). Idempotente: reconectar com o MESMO
        caminho não grava nada de novo.
      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: >-
            Caminho lexicamente inválido, ou o projeto não está no modo
            "runner".
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Projeto não existe.
      security:
        - service-token: []
      summary: O runner confirma o caminho de um projeto "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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '403':
          description: Service token ausente ou diferente do compartilhado.
      security:
        - service-token: []
      summary: Removes QA instances whose gate is no longer in flight (RN-937)
      tags:
        - internal
  /internal/rag/feedback:
    post:
      description: >-
        Mesmo caso de uso da rota humana — nenhum segundo caminho de julgamento.
        `searchId`/`chunkId` desconhecido é um 400 que o engine transforma em
        tool-result de erro para o modelo corrigir (RN-061), nunca um crash.
      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` desconhecido para este projeto, ou um `chunkId` que não
            estava entre os acertos daquela busca.
        '403':
          description: Service token ausente ou diferente do compartilhado.
      security:
        - service-token: []
      summary: O voto do agente sobre um chunk recuperado (RN-480)
      tags:
        - internal
  /internal/rag/search:
    post:
      description: >-
        Mesmo motor de `POST /projects/:projectId/rag/search` (rota humana),
        projetado no formato que a ferramenta `rag_search` do engine espera.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '403':
          description: Service token ausente ou diferente do compartilhado.
      security:
        - service-token: []
      summary: Busca híbrida sobre o índice RAG do projeto, para a ferramenta do engine
      tags:
        - internal
  /internal/sessions/{sessionId}/actions:
    post:
      description: >-
        A ÚNICA porta pela qual um agente toca git, terminal ou gasto.
        `permissions.json` decide na criação, e `deny` vence qualquer autonomia
        concedida ao agente.
      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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Propõe uma ação com efeito externo em nome de um agente
      tags:
        - internal
  /internal/sessions/{sessionId}/anamnese-context:
    get:
      description: >-
        Tudo numa chamada só, espelhando o contexto do Psicólogo. Os membros já
        vêm SEM quem optou por não ser perfilado, e as decisões do usuário
        dentro da janela vêm por aqui porque não estão no event log. O engine lê
        os eventos em si direto do Postgres — carregá-los por HTTP seria mais
        caro sem ser mais correto.
      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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Monta o contexto de uma rodada da Anamnese
      tags:
        - internal
  /internal/sessions/{sessionId}/anamnese-window:
    post:
      description: >-
        Grava uma rodada da Anamnese com zero perfis para que a próxima comece
        em windowTo — sem isso, a janela nunca avançava e todo tick pagava uma
        rodada nova sobre o mesmo material.
      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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Fecha a janela da Anamnese de uma rodada que não emitiu perfil
      tags:
        - internal
  /internal/sessions/{sessionId}/c4-diagram:
    post:
      description: >-
        O artefato É o evento `artifact.c4_diagram`: imutável, versionado e com
        autor, ao lado de `artifact.module_map`. Exige um module_map vigente —
        sem ele não há nível Container para desenhar (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` ausente, ator inválido, ou sem module_map vigente.'
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Gera uma versão nova do diagrama C4 (Context + Container)
      tags:
        - internal
  /internal/sessions/{sessionId}/delegations:
    post:
      description: >-
        `completed` (com o veredito), `failed` (com a origem) ou `dispensed`
        (com a justificativa) — o lead nunca chama esta rota com desfecho pela
        metade: toda delegação já nasce aqui resolvida.
      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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Registra o desfecho de uma delegação de área
      tags:
        - internal
  /internal/sessions/{sessionId}/dev-context:
    get:
      description: >-
        Uma chamada só com tudo de que o prompt precisa: a história inteira, as
        regras de negócio resolvidas e os ADRs aplicáveis. `module` restringe os
        ADRs ao módulo daquele dev; ADR sem módulo declarado é TRANSVERSAL e
        sempre entra. Omitir `module` traz a coleção inteira, que é o que os
        gates de QA e SecOps querem ao reusar este mesmo contexto.
      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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Monta o contexto completo de uma tarefa para o 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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Cria um épico do backlog
      tags:
        - internal
  /internal/sessions/{sessionId}/events:
    get:
      description: >-
        Usada para REIDRATAR o histórico de um agente conversacional e para ler
        os artefatos de que o kickoff dele precisa. A rota humana equivalente é
        protegida por RBAC; esta, pelo service token. `latest=true` devolve a
        CAUDA (ainda em `seq` crescente) e ignora `afterSeq`; `types` restringe
        a página a esses tipos de evento. A página tem teto de 200 em qualquer
        caso (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: Busca a cauda do log; ignora `afterSeq`.
          schema:
            example: 'true'
            type: string
        - name: types
          required: false
          in: query
          description: >-
            Tipos de evento separados por vírgula (no máximo 20). Só eventos
            desses tipos contam para `limit`.
          schema:
            example: artifact.product_brief,artifact.business_rule
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaginaDeEventosResponseDto'
        '400':
          description: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Pagina o event log da sessão para o engine
      tags:
        - internal
    post:
      description: >-
        Mesmo caso de uso e mesma atribuição atômica de `seq` da rota humana. As
        hipóteses do Psicólogo NÃO passam por aqui: têm rota própria, com
        validação de evidência.
      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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Acrescenta um evento ao log da sessão, em nome de um agente
      tags:
        - internal
  /internal/sessions/{sessionId}/gates/verdict:
    post:
      description: >-
        O `nextAction` da resposta é o que o engine obedece: `correct` volta ao
        dev, `run_secops` avança o gate, `done` libera para o usuário,
        `await_user` estaciona o ciclo até a pergunta aberta ser respondida
        (RN-860, sem contar correção), e `blocked` significa que o teto de
        correções acabou. O MERGE nunca é automático — `done` só quer dizer que
        é a vez do humano.
      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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Registra o veredito de QA ou SecOps sobre a PR de uma tarefa
      tags:
        - internal
  /internal/sessions/{sessionId}/handoffs:
    post:
      description: >-
        Nasce `offered`. Quem aceita é uma PESSOA, pela rota humana — agente não
        ativa agente. Duas exceções. A oferta Criativo→PO que leva o
        `product_brief` pedido por um clique em "Estou pronto — a necessidade
        está validada" é aceita em nome de quem clicou, com essa pessoa como
        ator e `implicito` no payload (RN-658, ADR 0185). O handoff do PO ao
        Arquiteto é aceito pelo SISTEMA quando o backlog está coberto (ao menos
        uma regra de negócio, nenhuma sem história) e o repositório é `local`
        sem credencial de git no projeto (RN-660, ADR 0186); `aceiteAutomatico`
        diz se aconteceu e, se não, por quê. Em qualquer dos casos a resposta
        então traz `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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
        '409':
          description: >-
            `agente_ja_ativo` (ADR 0182, RN-635): o alvo já está ativo numa
            sessão não encerrada do projeto. A `message` é o texto que o agente
            lê como resultado da ferramenta.
      security:
        - service-token: []
      summary: Oferece um handoff de um agente para outro
      tags:
        - internal
  /internal/sessions/{sessionId}/hypotheses:
    post:
      description: >-
        Cada hipótese precisa citar eventos que EXISTEM nesta sessão — evidência
        inventada é recusada com 400, e é isso que separa uma hipótese de uma
        opinião. A rodada anterior da sessão passa a superada.
      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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
        '409':
          description: Já existe uma análise vigente para esta sessão.
      security:
        - service-token: []
      summary: Registra uma rodada de análise e suas hipóteses
      tags:
        - internal
  /internal/sessions/{sessionId}/infra-artifacts/{prActionId}/files:
    get:
      description: >-
        O conteúdo vem do próprio payload da `proposed_action`: artefato de
        infra nunca toca worktree, como os ADRs.
      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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Devolve os arquivos de uma PR de infra para os gates lerem
      tags:
        - internal
  /internal/sessions/{sessionId}/infra-context:
    get:
      description: >-
        O module_map vigente mais os ADRs de infraestrutura relevantes, mais o
        roteamento de módulos do Arquiteto (ADR 0131) — as candidatas entre as
        quais o Infra Lead elege ao propor 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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Monta o contexto inicial do InfraAgent
      tags:
        - internal
  /internal/sessions/{sessionId}/infra-gates/verdict:
    post:
      description: Mesmo pipeline e mesma máquina de estados das PRs de 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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Registra o veredito de QA ou SecOps sobre uma PR de infra
      tags:
        - internal
  /internal/sessions/{sessionId}/instruction-patches:
    post:
      description: >-
        Vira `proposed_action`, não escrita direta: mudar o comportamento de um
        agente é efeito externo e passa pelo mesmo pipeline de aprovação de todo
        o resto. Isso fecha o laço hipótese → patch.
      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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Propõe um patch no arquivo de instruções de um agente
      tags:
        - internal
  /internal/sessions/{sessionId}/llm-turn:
    post:
      description: >-
        O metering é OBRIGATÓRIO: todo turno grava `token_usage`, e é isso que
        dá sentido ao orçamento. Não grava evento nenhum — quem narra o event
        log é o engine. Falha de provider volta em `error` com 200, porque a
        contabilidade em `usage` continua valendo: o turno gastou mesmo
        falhando.
      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: Corpo inválido.
        '403':
          description: Orçamento estourado com `policy=block`, ou service token inválido.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Executa um turno de LLM medido, com suporte a ferramentas
      tags:
        - internal
  /internal/sessions/{sessionId}/llm-turn-stream:
    post:
      description: >-
        Mesma semântica de `llm-turn`, entregue quadro a quadro. O quadro `done`
        traz o `usage` — sem ele o turno sairia sem contabilidade.
      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: Fluxo de quadros até `done` ou `error`.
          content:
            text/event-stream:
              schema:
                $ref: '#/components/schemas/LlmTurnStreamEventResponseDto'
        '400':
          description: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Executa um turno de LLM com a resposta em streaming
      tags:
        - internal
  /internal/sessions/{sessionId}/max-parallel-proposals:
    post:
      description: >-
        Recusa propor teto igual ou menor que o atual: a Anamnese roda
        periodicamente e repropria a mesma coisa a cada rodada, enchendo de
        ruído uma fila que o usuário precisa ler.
      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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Propõe subir o teto de paralelismo de uma área
      tags:
        - internal
  /internal/sessions/{sessionId}/module-contracts:
    post:
      description: >-
        O artefato É o evento `artifact.module_contracts`: imutável, versionado
        e com autor, ao lado de `artifact.module_map`. Cada chamada leva a lista
        INTEIRA e substitui a versão anterior. O que um módulo consome não se
        escreve aqui: é o `dependsOn` do module_map vigente.
      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: >-
            Lista vazia, módulo repetido, módulo fora do module_map vigente (ou
            sem module_map), módulo com `expoe` vazio ou grande demais, item com
            `tipo` desconhecido, ou `assinatura` ausente/grande demais.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Declara uma versão nova dos contratos entre módulos
      tags:
        - internal
  /internal/sessions/{sessionId}/module-map:
    post:
      description: >-
        O histórico é imutável: cada publicação é uma versão a mais, e a vigente
        é a de maior `version`. CICLO de dependência entre módulos faz o mapa
        ser RECUSADO com 400 — o grafo precisa ser acíclico.
      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: Ciclo de dependência entre módulos.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Publica uma versão nova do module_map
      tags:
        - internal
  /internal/sessions/{sessionId}/module-routing:
    post:
      description: >-
        O artefato É o evento `artifact.module_routing`: imutável, versionado e
        com autor, ao lado de
        `artifact.module_map`/`artifact.project_image`/`artifact.c4_diagram`.
        Cada item é validado com a mesma régua de `choose_project_image`
        (tag/digest explícito, `latest` recusado, `rationale` não trivial); nome
        de módulo fora do module_map vigente, lista vazia ou módulo repetido são
        recusados com 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: >-
            Lista vazia, módulo repetido, módulo fora do module_map vigente, ou
            imagem candidata que reprova na régua de `choose_project_image` (sem
            tag explícita, `latest`, ou `rationale` curto).
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Roteia cada módulo do module_map vigente para uma imagem candidata
      tags:
        - internal
  /internal/sessions/{sessionId}/pending-work:
    get:
      description: >-
        Consultada pelo `SessionServer` do engine no timeout de heartbeat.
        `pending: true` faz o timeout ser reagendado em vez de fechar, e o
        `motivo` vai para o log do engine — sessão que não fecha sem dizer por
        quê é indiagnosticável.
      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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: A sessão tem trabalho pendente que impede fechá-la?
      tags:
        - internal
  /internal/sessions/{sessionId}/proficiency:
    post:
      description: >-
        Competência fora do catálogo é RECUSADA, e evidência que cita evento de
        outro projeto também — as duas validações existem porque o modelo erra
        as duas coisas.
      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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Registra os perfis de proficiência derivados na rodada
      tags:
        - internal
  /internal/sessions/{sessionId}/project-image:
    post:
      description: >-
        O artefato É o evento `artifact.project_image`: imutável, versionado e
        com autor. Revisar é emitir versão nova, nunca sobrescrever. Enquanto
        não houver uma, o container do projeto não sobe e a aba Código responde
        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: >-
            Imagem sem tag explícita (ou `latest`), `rationale` curto demais,
            `network` fora de {none, egress}, ou recurso acima do teto.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Define a imagem de container do projeto
      tags:
        - internal
  /internal/sessions/{sessionId}/psychologist-context:
    get:
      description: >-
        `alreadyAnalyzed` é o que dá IDEMPOTÊNCIA ao caminho automático: com
        `true` o worker encerra cedo sem gastar token. As hipóteses anteriores
        vêm junto para a rodada não se repetir.
      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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Monta o contexto de uma rodada do Psicólogo
      tags:
        - internal
  /internal/sessions/{sessionId}/semantic-duplicate-check:
    post:
      description: >-
        Vetoriza o título e os títulos das regras do projeto (as 100 mais
        recentes) com o modelo de embedding do RAG e avisa com cosseno ≥ o
        limiar. Nunca recusa. Sem provider de embedding a checagem é PULADA com
        o motivo, narrado também no event log. O gasto de embedding é medido
        como linha própria de `token_usage` (ator
        `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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Avisa quando uma regra de negócio recém-escrita parece com uma existente
      tags:
        - internal
  /internal/sessions/{sessionId}/stories:
    post:
      description: >-
        `businessRuleIds` é o que alimenta a cobertura regra→história. Cada id
        tem de referenciar um evento `artifact.business_rule` que EXISTE — a
        validação recusa id inventado. A resposta traz também
        `semanticDuplicate` (RN-681): um AVISO por embedding contra as histórias
        do projeto, nunca uma recusa.
      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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: >-
        Cria uma história com requisitos funcionais/não funcionais, DoD, DoR e
        regras cobertas
      tags:
        - internal
  /internal/sessions/{sessionId}/stories/{storyId}/archive:
    post:
      description: >-
        A história e suas tarefas saem do backlog, da cobertura, do plano e do
        claim; a linha fica e `backlog.story_archived` a registra, com o PO como
        ator (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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: A história não é deste projeto.
        '409':
          description: >-
            Recusa nomeada (`reason`): `historia_arquivada`,
            `historia_nao_draft` ou `historia_com_tarefa_em_execucao`.
      security:
        - service-token: []
      summary: Arquiva uma história em rascunho
      tags:
        - internal
  /internal/sessions/{sessionId}/stories/{storyId}/complete:
    post:
      description: >-
        `businessRuleIds` é SOMADO às regras já ligadas; listas não vazias
        substituem as atuais e listas omitidas ficam. Depois de gravar, a
        história é promovida pelo MESMO critério da criação de história (`ready`
        no modo `auto`, `proposedReady` no `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: Um business_rule_id não referencia uma regra existente.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: A história não é deste projeto.
        '409':
          description: A história não está mais em `draft`.
      security:
        - service-token: []
      summary: Completa uma história em rascunho existente em vez de recriá-la
      tags:
        - internal
  /internal/sessions/{sessionId}/stories/{storyId}/update:
    post:
      description: >-
        Só história `draft` sem tarefa em execução; grava
        `backlog.story_updated` com o PO como ator (RN-727). Uma `ready` também,
        quando `questionId` cita uma pergunta aberta que o usuário já respondeu
        (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: Nenhum campo, ou título vazio.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: A história não é deste projeto.
        '409':
          description: >-
            Recusa nomeada (`reason`): `historia_arquivada`,
            `historia_nao_draft` ou `historia_com_tarefa_em_execucao`.
      security:
        - service-token: []
      summary: Corrige o título e/ou a descrição de uma história em rascunho
      tags:
        - internal
  /internal/sessions/{sessionId}/story-modules:
    post:
      description: >-
        Esta é a validação cruzada: módulo que não existe no mapa vigente vira
        pendência de arquitetura em vez de passar despercebido.
      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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Associa uma história aos módulos que ela toca
      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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Cria uma tarefa dentro de uma história
      tags:
        - internal
  /internal/sessions/{sessionId}/tasks/{taskId}/block:
    post:
      description: >-
        Não há desbloqueio automático: quem desbloqueia é uma pessoa, pela rota
        humana. É isso que impede um agente de girar indefinidamente.
      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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Marca a tarefa como bloqueada, com o motivo
      tags:
        - internal
  /internal/sessions/{sessionId}/tasks/{taskId}/gate/open:
    post:
      description: Chamada quando a PR do dev agent está pronta para revisão.
      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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Abre o pipeline de gates da tarefa, começando por 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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
        '409':
          description: Transição inválida, ou tarefa de outro agente.
      security:
        - service-token: []
      summary: Move o estado da tarefa conforme o trabalho avança
      tags:
        - internal
  /internal/sessions/{sessionId}/tasks/{taskId}/unblock:
    post:
      description: >-
        Chamada pelo job de retentativa do engine (até 3 vezes, depois de 1, 3 e
        10 min). Mesmo caso de uso do desbloqueio humano, com ator `system`;
        tarefa que não está mais bloqueada fica intocada.
      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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Desbloqueia uma tarefa depois de uma falha transitória de rede/provider
      tags:
        - internal
  /internal/sessions/{sessionId}/tasks/claim:
    post:
      description: >-
        ATÔMICA por desenho: com vários dev agents no mesmo módulo, dois claims
        concorrentes não podem devolver a mesma tarefa. Sem tarefa disponível,
        devolve vazio em vez de erro.
      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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Reivindica atomicamente a próxima tarefa reivindicável do módulo
      tags:
        - internal
  /internal/sessions/{sessionId}/tasks/in-progress-by-dev:
    get:
      description: >-
        Não bloqueadas e fora dos gates de PR; vazio a menos que a sessão seja
        de execução. Usada pelo engine ao parar uma execução, para bloquear as
        que nenhum dev agent vivo segura.
      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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Lista as tarefas em andamento do projeto seguradas por um dev agent
      tags:
        - internal
  /internal/sessions/{sessionId}/termination:
    post:
      description: >-
        Só chegam aqui os encerramentos que a api NÃO causou — crash, kill,
        `heartbeat_timeout`, desligamento defensivo. Uma parada planejada pela
        própria api não passa por aqui: o engine já soube dela pela outbox, e
        relatá-la de volta seria eco.
      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: Corpo inválido.
        '403':
          description: Service token ausente ou diferente do compartilhado.
        '404':
          description: Sessão, projeto ou recurso não encontrado.
      security:
        - service-token: []
      summary: Relata que o processo da sessão terminou no engine
      tags:
        - internal
  /live:
    get:
      description: >-
        NÃO toca o banco, de propósito. Se tocasse, um Postgres lento
        reiniciaria TODAS as réplicas de uma vez e transformaria degradação em
        queda total. O pod sai do balanceador pelo `/health`.
      operationId: HealthController_live
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthStatusResponseDto'
      summary: Liveness — o processo ainda atende requisições
      tags:
        - infrastructure
  /llm/provider-capabilities:
    get:
      description: >-
        A camada de PROVIDER das capabilities (ADR 0041) para os nove providers,
        lida das mesmas instâncias que servem as chamadas. Uma capability só é
        `true` quando provada contra a API real. A tela lê `routingPreference`
        daqui antes de oferecer preferência de roteamento num binding (ADR
        0166).
      operationId: ProviderCapabilitiesController_list
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ProviderCapabilitiesResponseDto'
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lista o que cada provider de LLM sabe fazer, independente do modelo
      tags:
        - llm
  /metrics:
    get:
      description: >-
        Não é JSON: é `text/plain` no formato de exposição. Pública por
        necessidade (o scraper não carrega token) e restrita por REDE — o
        Ingress de produção bloqueia este caminho na borda e a NetworkPolicy só
        libera o namespace de monitoramento.
      operationId: MetricsController_scrape
      parameters: []
      responses:
        '200':
          description: Métricas no formato de exposição.
          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: Expõe métricas no formato de exposição do 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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: O projeto não existe ou é invisível para quem chama.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Devolve um projeto pelo 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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: O projeto não existe ou é invisível para quem chama.
        '409':
          description: Já existe um projeto com este slug no workspace.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Muda o nome ou o slug do projeto
      tags:
        - projects
    delete:
      description: Leva junto as sessões, o event log e as ações propostas.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: O projeto não existe ou é invisível para quem chama.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Remove o projeto
      tags:
        - projects
  /projects/{projectId}/actions:
    get:
      description: >-
        `status` é `pending` (o padrão) ou `failed`. `actionType` filtra por
        tipo — ex.: `git_merge`, para a aba de PRs achar a proposta de merge de
        uma PR sem saber qual sessão a criou. `status=failed` EXIGE `actionType`
        e lista as ações cuja execução falhou — a aba de PRs lê dali o último
        merge recusado de cada PR (conflito de merge traz
        `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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lista as ações PENDENTES do projeto inteiro, de qualquer sessão
      tags:
        - actions
  /projects/{projectId}/agent-areas:
    get:
      description: >-
        O projeto nasce com as três áreas (RN-094), então a lista volta cheia
        mesmo antes de existir `module_map`. O que ativar a execução acrescenta
        são os MEMBROS da área de dev, um por módulo.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou tarefa não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: As áreas de agentes do projeto, com o teto de cada lead
      tags:
        - execution
  /projects/{projectId}/agent-areas/{key}/budget:
    put:
      description: >-
        Exige `maintainer`, pelo mesmo motivo do teto de paralelismo: é uma
        checagem INDEPENDENTE e aditiva ao lado dos orçamentos de projeto e de
        sessão (ADR 0110) — não é cascata, nem substitui nenhum dos dois. O
        limite entra em DÓLARES e é convertido para micro-USD no servidor, a
        mesma convenção do `BudgetsController`. `null` limpa o teto.
      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` não é `null` nem um número >= 0.'
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou tarefa não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Define (ou limpa) o teto de gasto de uma área
      tags:
        - execution
  /projects/{projectId}/agent-areas/{key}/max-parallel:
    patch:
      description: >-
        Exige `maintainer`, não `developer`, pelo mesmo motivo de ativar a
        execução: subir o teto é decidir quanto o produto pode gastar sem
        perguntar. Vale para os PRÓXIMOS pedidos — o que já aguarda a sua
        decisão continua aguardando.
      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` não é um inteiro >= 1.'
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou tarefa não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Muda o teto de paralelismo de uma área
      tags:
        - execution
  /projects/{projectId}/agent-autonomy:
    get:
      description: >-
        Só as regras gravadas explicitamente. Agente sem linha aqui cai no
        padrão do `permissions.json` do projeto.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lista a autonomia concedida a cada agente
      tags:
        - actions
    put:
      description: >-
        Upsert por (agente, tipo). NÃO sobrepõe o `permissions.json`: padrão já
        em `deny` continua bloqueado por mais autonomia que se conceda aqui.
        `actionType: "*"` é o "modo automático" (RN-153) — autonomia para
        QUALQUER tipo de ação deste agente; regra específica gravada depois
        continua vencendo a curinga para aquele tipo.
      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: Regra gravada. Sem corpo.
        '400':
          description: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Define a autonomia de um agente para um tipo de ação
      tags:
        - actions
  /projects/{projectId}/agent-bindings/{agentSlug}:
    get:
      description: >-
        Cascata agente → área → projeto → workspace, SEM sessão: é a
        configuração do agente no projeto, não a de uma conversa específica.
        `origin: "agent"` quer dizer que este agente DIVERGIU do padrão da área;
        qualquer outra origem quer dizer que ele herda.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no escopo.
        '404':
          description: Escopo não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Resolve qual modelo um agente usa, e de onde veio
      tags:
        - llm
    put:
      description: >-
        Vale para toda sessão deste projeto que não tem binding próprio, e só
        para elas — antes do ADR 0064 o binding era global e alcançava outros
        projetos também. É assim que se dá um modelo barato ao Psicólogo e um
        caro ao Arquiteto no mesmo projeto, e também é como um agente DIVERGE do
        padrão da área.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no escopo.
        '404':
          description: Escopo não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Fixa o modelo de um agente NESTE projeto
      tags:
        - llm
    delete:
      description: >-
        APAGA o binding do agente — não copia o modelo da área para ele. Copiar
        pareceria igual na tela e não é: viraria uma cópia, e a próxima mudança
        da área deixaria este agente para trás em silêncio. 404 quando ele já
        herda.
      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: O agente voltou a herdar. Sem corpo.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no escopo.
        '404':
          description: Escopo não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Faz o agente voltar a herdar o modelo da área
      tags:
        - llm
  /projects/{projectId}/agent-costs:
    get:
      description: >-
        Janela DESLIZANTE de 30 dias, não o mês civil corrente: é o rótulo que a
        tela mostra, e com mês civil a estimativa despencaria no dia 1º por
        virar a página, não por mudança de uso. Só conta `actor_kind = agent`
        (RN-038). Agente que nunca rodou não aparece na lista — a tela mostra um
        traço para ele, o que é diferente de zero.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto ou sessão não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Quebra o gasto do projeto por agente, nos últimos 30 dias
      tags:
        - llm
  /projects/{projectId}/agents/{agent}/instruction-versions:
    get:
      description: >-
        A mais recente primeiro, cada uma já trazendo o diff contra a anterior,
        calculado no servidor.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, evento ou versão não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lista as versões de instrução de um agente
      tags:
        - anamnesis
  /projects/{projectId}/agents/{agent}/instruction-versions/{version}/rollback:
    post:
      description: >-
        O histórico é imutável: restaurar não apaga nada, CRIA uma versão nova
        com o conteúdo antigo. Exige `maintainer` porque muda o comportamento do
        agente dali em diante — mesmo calibre do patch.
      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: Versão a restaurar.
          schema:
            example: 2
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RollbackResponseDto'
        '400':
          description: A versão não é um inteiro positivo.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, evento ou versão não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Restaura uma versão anterior da instrução de um agente
      tags:
        - anamnesis
  /projects/{projectId}/anamnese/run:
    post:
      description: >-
        Exige `maintainer` pelo mesmo motivo da reanálise do Psicólogo: roda o
        ToolLoop e gasta orçamento real.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, evento ou versão não existe.
        '429':
          description: Rate limit por usuário ou por IP.
        '503':
          description: >-
            A Anamnese está desligada globalmente por decisão do usuário (não é
            bug) — corpo com `reason: "anamnese_disabled"`.
      security:
        - bearer: []
      summary: Roda a Anamnese agora, sem esperar o tick
      tags:
        - anamnesis
  /projects/{projectId}/architecture:
    get:
      description: >-
        O mapa vigente é o de maior `version` (o histórico é imutável). As
        pendências são a validação cruzada história↔mapa: história sem módulo,
        ou apontando para módulo que não existe.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Devolve o module_map vigente, os ADRs e as pendências
      tags:
        - backlog
  /projects/{projectId}/area-bindings/{areaKey}:
    get:
      description: >-
        O padrão que o lead e os subagentes da área compartilham. `origin:
        "area"` quer dizer que alguém o escolheu para esta área; qualquer outra
        origem quer dizer que a própria área herda do projeto ou do workspace.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no escopo.
        '404':
          description: Escopo não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Resolve qual é o modelo padrão de uma área, e de onde veio
      tags:
        - llm
    put:
      description: >-
        Vale para o lead e para todo subagente da área que não tem binding
        próprio (RN-102). Exige `maintainer` porque alcança a área inteira de
        uma vez, e escolher modelo é decidir gasto — o mesmo motivo do teto de
        paralelismo.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no escopo.
        '404':
          description: Escopo não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Define o modelo padrão de uma área
      tags:
        - llm
    delete:
      description: >-
        A área deixa de ter padrão próprio e passa a herdar do projeto (ou do
        workspace). Agentes que divergiram continuam divergindo: o binding deles
        é outro, e apagar este não pode decidir por eles.
      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: A área voltou a herdar. Sem corpo.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no escopo.
        '404':
          description: Escopo não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Faz a área voltar a herdar o modelo do projeto
      tags:
        - llm
  /projects/{projectId}/backlog:
    get:
      description: >-
        Aninhada e completa, numa chamada só. Histórias ARQUIVADAS (RN-727) não
        são listadas. O backlog é escrito por agentes, pelas rotas
        `/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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Devolve a árvore épico → história → tarefa
      tags:
        - backlog
  /projects/{projectId}/budget:
    get:
      description: Valores em micro-USD. Exige `maintainer` — é informação de custo.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto ou sessão não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lê o orçamento do projeto e quanto já foi gasto
      tags:
        - llm
    put:
      description: >-
        O limite entra em DÓLARES e é convertido para micro-USD. Com
        `policy=block` a chamada que estouraria o teto é RECUSADA, não só
        registrada.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto ou sessão não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Define o teto de gasto do projeto
      tags:
        - llm
  /projects/{projectId}/code/blame:
    get:
      description: >-
        Base para a pendência declarada do blame (FASE 26b) — a interface que
        anota o editor linha a linha ainda não existe. `truncated` avisa quando
        o arquivo passou do teto de linhas anotadas.
      operationId: CodeController_blame
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: path
          required: true
          in: query
          description: Caminho do arquivo a anotar.
          schema:
            type: string
        - name: ref
          required: false
          in: query
          description: Branch, tag ou sha. Ausente usa a branch padrão do repositório.
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CodeBlameResponseDto'
        '400':
          description: >-
            Caminho fora do escopo do projeto (RN-095), ref malformada, ou termo
            de busca curto ou longo demais.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto sem repositório provisionado, ou ref/caminho/PR inexistente.
        '429':
          description: Rate limit por usuário ou por IP.
        '501':
          description: >-
            O provider do projeto não declara a capability da operação —
            `listTree`, `pullRequestDiff`, `blame`, `pullRequestsList` ou
            `branchesDetailed`. Capability só é declarada depois de provada pela
            suíte de contrato, então esta é uma resposta legítima, não um
            defeito.
      security:
        - bearer: []
      summary: Anota cada linha de um arquivo com o commit que a tocou
      tags:
        - git
  /projects/{projectId}/code/branches:
    get:
      description: >-
        Base para o dropdown rico (FASE 26b) — hoje o seletor de ref é um campo
        de texto simples. `ahead`/`behind` são relativos à branch PADRÃO do
        repositório; `null` quando o provider não consegue calcular (degradação
        honesta, não erro). NÃO é `listBranches` (que o bootstrap do Gitflow
        usa) — é operação própria, porque enriquecer custa uma chamada a mais ao
        provider POR BRANCH.
      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: >-
            Caminho fora do escopo do projeto (RN-095), ref malformada, ou termo
            de busca curto ou longo demais.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto sem repositório provisionado, ou ref/caminho/PR inexistente.
        '429':
          description: Rate limit por usuário ou por IP.
        '501':
          description: >-
            O provider do projeto não declara a capability da operação —
            `listTree`, `pullRequestDiff`, `blame`, `pullRequestsList` ou
            `branchesDetailed`. Capability só é declarada depois de provada pela
            suíte de contrato, então esta é uma resposta legítima, não um
            defeito.
      security:
        - bearer: []
      summary: Lista branches com ahead/behind e a PR aberta associada
      tags:
        - git
  /projects/{projectId}/code/file:
    get:
      description: >-
        UTF-8, cortado no teto de bytes com `truncated: true` quando passa dele.
        Caminho que escapa da pasta do projeto é recusado com 400 pela checagem
        central da RN-095 — nunca com 404, que convidaria a sondar.
      operationId: CodeController_file
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: path
          required: true
          in: query
          description: Caminho do arquivo.
          schema:
            type: string
        - name: ref
          required: false
          in: query
          description: Branch, tag ou sha.
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CodeFileResponseDto'
        '400':
          description: >-
            Caminho fora do escopo do projeto (RN-095), ref malformada, ou termo
            de busca curto ou longo demais.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto sem repositório provisionado, ou ref/caminho/PR inexistente.
        '429':
          description: Rate limit por usuário ou por IP.
        '501':
          description: >-
            O provider do projeto não declara a capability da operação —
            `listTree`, `pullRequestDiff`, `blame`, `pullRequestsList` ou
            `branchesDetailed`. Capability só é declarada depois de provada pela
            suíte de contrato, então esta é uma resposta legítima, não um
            defeito.
      security:
        - bearer: []
      summary: Devolve o conteúdo de um arquivo
      tags:
        - git
  /projects/{projectId}/code/pull-requests:
    get:
      description: >-
        Base para a lista navegável (FASE 26b) — hoje o diff só é alcançável por
        um id conhecido (ex.: vindo de Aprovações); esta rota resolve como
        chegar ao id sem ter de sabê-lo de cor. `truncated` avisa quando a lista
        passou do teto por chamada.
      operationId: CodeController_pullRequests
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: state
          required: false
          in: query
          description: Ausente lista todos os estados.
          schema:
            enum:
              - open
              - merged
              - closed
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CodePullRequestListResponseDto'
        '400':
          description: >-
            Caminho fora do escopo do projeto (RN-095), ref malformada, ou termo
            de busca curto ou longo demais.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto sem repositório provisionado, ou ref/caminho/PR inexistente.
        '429':
          description: Rate limit por usuário ou por IP.
        '501':
          description: >-
            O provider do projeto não declara a capability da operação —
            `listTree`, `pullRequestDiff`, `blame`, `pullRequestsList` ou
            `branchesDetailed`. Capability só é declarada depois de provada pela
            suíte de contrato, então esta é uma resposta legítima, não um
            defeito.
      security:
        - bearer: []
      summary: Lista as PRs/MRs do repositório
      tags:
        - git
  /projects/{projectId}/code/pull-requests/{pullRequestId}/diff:
    get:
      description: >-
        `patch` nulo distingue o caso em que o provider não entregou o texto
        (binário, ou patch grande demais) do caso em que o texto voltou vazio.
        Juntar os dois faria a tela dizer que um binário alterado não mudou. O
        campo `truncated` avisa quando a lista de arquivos passou do teto.
      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: >-
            Caminho fora do escopo do projeto (RN-095), ref malformada, ou termo
            de busca curto ou longo demais.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto sem repositório provisionado, ou ref/caminho/PR inexistente.
        '429':
          description: Rate limit por usuário ou por IP.
        '501':
          description: >-
            O provider do projeto não declara a capability da operação —
            `listTree`, `pullRequestDiff`, `blame`, `pullRequestsList` ou
            `branchesDetailed`. Capability só é declarada depois de provada pela
            suíte de contrato, então esta é uma resposta legítima, não um
            defeito.
      security:
        - bearer: []
      summary: Devolve o diff de uma PR, normalizado entre providers
      tags:
        - git
  /projects/{projectId}/code/search:
    get:
      description: >-
        A busca NÃO é operação do contrato de git: é composta sobre a árvore e o
        conteúdo, e por isso é a única leitura cujo custo cresce com o tamanho
        do repositório. Três orçamentos a limitam — diretórios percorridos,
        arquivos abertos e acertos devolvidos — e `truncated` diz que ela parou.
        `filesScanned` mostra o custo que ela teve.
      operationId: CodeController_search
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: q
          required: true
          in: query
          description: Termo, de 2 a 200 caracteres.
          schema:
            type: string
        - name: ref
          required: false
          in: query
          description: Branch, tag ou sha.
          schema:
            type: string
        - name: path
          required: false
          in: query
          description: Subárvore onde buscar. Estreitar isto é o que evita o corte.
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CodeSearchResponseDto'
        '400':
          description: >-
            Caminho fora do escopo do projeto (RN-095), ref malformada, ou termo
            de busca curto ou longo demais.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto sem repositório provisionado, ou ref/caminho/PR inexistente.
        '429':
          description: Rate limit por usuário ou por IP.
        '501':
          description: >-
            O provider do projeto não declara a capability da operação —
            `listTree`, `pullRequestDiff`, `blame`, `pullRequestsList` ou
            `branchesDetailed`. Capability só é declarada depois de provada pela
            suíte de contrato, então esta é uma resposta legítima, não um
            defeito.
      security:
        - bearer: []
      summary: Busca texto no repositório, com orçamento
      tags:
        - git
  /projects/{projectId}/code/tree:
    get:
      description: >-
        Não recursiva, por desenho do contrato: a aba navega sob demanda, e
        pedir a árvore inteira de um repositório grande é exatamente o
        amplificador de tráfego que esta fase proíbe. `truncated` avisa quando o
        nível passou do teto de entradas.
      operationId: CodeController_tree
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: ref
          required: false
          in: query
          description: Branch, tag ou sha. Ausente usa a branch padrão do repositório.
          schema:
            type: string
        - name: path
          required: false
          in: query
          description: Diretório a listar. Ausente ou vazio é a raiz.
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CodeTreeResponseDto'
        '400':
          description: >-
            Caminho fora do escopo do projeto (RN-095), ref malformada, ou termo
            de busca curto ou longo demais.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto sem repositório provisionado, ou ref/caminho/PR inexistente.
        '429':
          description: Rate limit por usuário ou por IP.
        '501':
          description: >-
            O provider do projeto não declara a capability da operação —
            `listTree`, `pullRequestDiff`, `blame`, `pullRequestsList` ou
            `branchesDetailed`. Capability só é declarada depois de provada pela
            suíte de contrato, então esta é uma resposta legítima, não um
            defeito.
      security:
        - bearer: []
      summary: Lista UM nível da árvore do repositório
      tags:
        - git
  /projects/{projectId}/container:
    get:
      description: >-
        Enquanto o status é `sem_decisao`, o container do projeto não sobe e a
        aba Código responde 409 — esse é o portão da RN-105. Ver o estado exige
        a mesma permissão de ver o projeto, então `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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Estado do container do projeto (a decisão de imagem vigente)
      tags:
        - projects
  /projects/{projectId}/container/lifecycle:
    get:
      description: >-
        `null` quando o projeto nunca foi provisionado — o caso comum hoje,
        porque nenhum orquestrador real transiciona esta tabela ainda
        (RN-243/RN-267). `status`/`imageVersion`/`resources` são o que foi
        REGISTRADO; `observado` é o que o daemon do Docker relata agora,
        perguntado pelo broker (ADR 0130). Os dois nunca se fundem: um container
        morto por fora aparece como `running` registrado e `exited` observado.
        Quando não houve como olhar, `observado` é `null` e `naoObservado` diz
        por quê — ele nunca herda o estado registrado (RN-468).
      operationId: ContainersController_cicloDeVida
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: O ciclo de vida registrado, ou `null` (nunca provisionado).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CicloDeVidaDoContainerResponseDto'
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Ciclo de vida do container do projeto (estado registrado)
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '429':
          description: Rate limit por usuário ou por 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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Declares the container network (human only)
      tags:
        - projects
  /projects/{projectId}/coverage:
    get:
      description: >-
        Cruza as regras do projeto com os `businessRuleIds` das histórias. Regra
        sem história nenhuma é uma DESCOBERTA — pendência para o PO, não erro.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Calcula a cobertura regra de negócio → histórias
      tags:
        - backlog
  /projects/{projectId}/events/{eventId}:
    get:
      description: >-
        Diferente da rota de evento por sessão: aqui a sessão não é conhecida. É
        isto que faz o chip de evidência do perfil cair no evento certo, já que
        a janela da Anamnese atravessa várias sessões.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, evento ou versão não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Devolve um evento do projeto pelo id, resolvendo a sessão dele
      tags:
        - anamnesis
  /projects/{projectId}/execution-mode:
    put:
      description: >-
        Migra `execution_mode`/`workspacePath` de um projeto que JÁ existe
        (RN-447..450, ADR 0111) — rota dedicada, separada do PATCH, porque
        orquestra mais do que gravar uma coluna: move o permissions.json para a
        raiz de escopo nova, aposenta a linha de ciclo de vida do container ao
        sair do modo `container`, e recusa (409) enquanto qualquer dev agent do
        projeto não estiver ocioso, já que um agente rodando não re-resolve
        sozinho onde está o worktree dele.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: O projeto não existe ou é invisível para quem chama.
        '409':
          description: Um dev agent deste projeto está trabalhando ou travado agora.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Converte o modo de execução do projeto
      tags:
        - projects
  /projects/{projectId}/execution/activate:
    post:
      description: >-
        Exige `maintainer`, não `developer`, porque as ações de git dos dev
        agents herdam o papel de QUEM ATIVOU na avaliação de IAM — ativar como
        developer deixaria os agentes sem conseguir abrir PR. Cada módulo do
        `module_map` ganha um agente num worktree isolado.
      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: >-
            Sem `module_map` vigente (o Arquiteto tem de definir os módulos
            primeiro), ou a localização de workspace gravada do projeto é
            incoerente — o `permissions.json` semeado aqui não pode ser derivado
            dela (RN-478).
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou tarefa não encontrado.
        '409':
          description: >-
            O projeto ainda não tem repositório (RN-582): os dev agents
            trabalham em worktrees dele, então nada começa. A mensagem nomeia o
            que falta — o handoff ao Arquiteto (ou, como segunda porta, ao Dev
            Lead) cujo aceite o provisiona, ou a página de provisionamento
            quando esse handoff já foi aceito e não deixou repositório. Também:
            a sessão de origem é `consultiva` e recusa `execution.activated`
            (RN-097). Ativar duas vezes NÃO é conflito: é idempotente por
            `findActiveExecutionSession`, e reativa dentro da mesma sessão.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Ativa a fase de execução e sobe um dev agent por módulo
      tags:
        - execution
  /projects/{projectId}/execution/session:
    get:
      description: >-
        A sessão `active` mais recente que já traz `execution.activated` — o
        MESMO critério que `activate` usa para decidir entre reativar ou criar
        uma (RN-139). `null` quando não há execução em andamento. Existe para a
        aba Executores parar de inferir esta sessão a partir da mais recente do
        projeto, que muda de sessão em sessão sem pista nenhuma na interface.
      operationId: ExecutionController_getSession
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: '`null` no corpo quando não há execução ativa.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionResponseDto'
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou tarefa não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Devolve a sessão de execução corrente do projeto, ou nada
      tags:
        - execution
  /projects/{projectId}/git/{provider}/connect:
    get:
      description: >-
        Não redireciona: devolve a URL para o cliente decidir quando mandar o
        navegador. O `state` vai assinado por HMAC, e é isso que impede o
        callback de ser forjado.
      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: Provider fora de `github`/`gitlab`.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Inicia o OAuth com o provider de git
      tags:
        - git
  /projects/{projectId}/git/{provider}/repository:
    post:
      description: >-
        SÍNCRONA: a resposta só volta depois que o bootstrap inteiro (branches
        permanentes, proteções, templates) rodou — não há worker nem fila por
        trás. É IDEMPOTENTE E RETOMÁVEL, então chamá-la de novo depois de uma
        falha retoma do passo que falhou. Acompanhe o progresso e leia o motivo
        da falha por `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: Provider inválido, ou credencial ausente.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Cria o repositório e roda o bootstrap do Gitflow
      tags:
        - git
  /projects/{projectId}/git/{provider}/repository/adopt:
    post:
      description: >-
        Valida o acesso com `getRepo` e produz um PLANO (dry-run) do que o
        bootstrap faria — branches faltando, proteções faltando, arquivos. NADA
        é executado no repositório: decida depois por `plan/approve` ou
        `plan/skip`. Readotar o mesmo repositório converge (regenera o plano sem
        duplicar linha).
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: O repositório existe, mas a credencial registrada não o alcança.
        '404':
          description: O repositório não existe no provider — confira o identificador.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Adota um repositório existente, sem criar nada
      tags:
        - git
  /projects/{projectId}/git/bootstrap:
    get:
      description: >-
        Em que passo está, se falhou e onde, e quantas tentativas houve. O
        detalhe passo a passo vem dos eventos `bootstrap.step_*` da sessão
        dedicada.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Devolve o estado do bootstrap do Gitflow
      tags:
        - git
  /projects/{projectId}/git/bootstrap/acknowledge-protection-failure:
    post:
      description: >-
        `protect_branches` falha em repositório privado no plano gratuito, e o
        assistente avisa disso ANTES. Sem esta saída o único botão era "Tentar
        de novo", que sempre falha pelo mesmo motivo — e `provision_failed` faz
        o painel redirecionar o projeto de volta para a página de
        provisionamento, deixando-o inalcançável para sempre. Só falha em
        PROTEGER pode ser reconhecida: é o último passo e o único cuja falha
        deixa um repositório utilizável. A trava de merge do produto (RN-006)
        não depende da proteção do provider e continua valendo.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não existe.
        '409':
          description: >-
            O bootstrap não falhou, ou falhou num passo anterior à proteção —
            seguir adiante ali deixaria o projeto sem repositório utilizável.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Reconhece a falha de proteção de branch e segue adiante
      tags:
        - git
  /projects/{projectId}/git/bootstrap/plan:
    get:
      description: >-
        `decision: null` com plano presente é o estado em que NADA roda —
        aguardando aprovação ou adoção como está (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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Devolve o plano de bootstrap e o que se decidiu sobre ele
      tags:
        - git
  /projects/{projectId}/git/bootstrap/plan/approve:
    post:
      description: >-
        A aprovação é tudo ou nada: aprovar passos soltos quebraria a cascata
        `dev←main, qa←dev, rc←qa`. O que roda é o plano RE-DERIVADO na hora da
        execução — igual ou menor que o mostrado, nunca maior.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não existe.
        '409':
          description: >-
            O plano foi regenerado desde que você o viu, ou já tinha sido
            decidido.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Aprova o plano INTEIRO e roda o bootstrap
      tags:
        - git
  /projects/{projectId}/git/bootstrap/plan/skip:
    post:
      description: >-
        Grava a decisão e não toca o repositório. O plano fica guardado como
        evidência do que deliberadamente não foi aplicado, e o cursor do
        bootstrap NÃO é mexido — nenhum passo rodou.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não existe.
        '409':
          description: >-
            O plano foi regenerado desde que você o viu, ou já tinha sido
            decidido.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Adota o repositório COMO ESTÁ, dispensando o 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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Devolve o repositório provisionado do projeto
      tags:
        - git
  /projects/{projectId}/hypotheses:
    get:
      description: >-
        Cada hipótese cita os eventos que a sustentam (`evidenceEventIds`) — é
        isso que a torna auditável em vez de aceita por fé.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou hipótese não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lista as hipóteses do Psicólogo sobre os agentes
      tags:
        - psychologist
  /projects/{projectId}/hypotheses/{hypothesisId}/accept:
    post:
      description: >-
        Move `proposed` → `accepted`. NÃO é `proposed_action`: o Psicólogo é
        somente leitura e nada aqui tem efeito externo.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou hipótese não existe.
        '409':
          description: A hipótese já foi decidida.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Aceita uma hipótese
      tags:
        - psychologist
  /projects/{projectId}/hypotheses/{hypothesisId}/dismiss:
    post:
      description: Move `proposed` → `dismissed`. Mesmo raciocínio do aceite.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou hipótese não existe.
        '409':
          description: A hipótese já foi decidida.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Descarta uma hipótese
      tags:
        - psychologist
  /projects/{projectId}/infra-artifacts:
    get:
      description: >-
        Artefatos do InfraAgent (Dockerfile, compose, CI). Passam pelos MESMOS
        gates de QA e SecOps das PRs de dev, sem tarefa nem worktree por trás.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lista as PRs de infra e o estágio de gate de cada uma
      tags:
        - backlog
  /projects/{projectId}/instruction-versions:
    get:
      description: >-
        Parte de quem TEM versão no projeto, não de uma lista estática — é isso
        que faz os dev agents por módulo (`dev-api`) aparecerem.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, evento ou versão não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lista o histórico de instruções de todo agente do projeto
      tags:
        - anamnesis
  /projects/{projectId}/members:
    get:
      description: >-
        Só quem foi associado AQUI, com o papel dessa associação — quem alcança
        o projeto só pelo workspace não tem linha e não aparece.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: O projeto não existe ou é invisível para quem chama.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lista os membros do projeto
      tags:
        - projects
    post:
      description: >-
        Este papel SOBREPÕE o que a pessoa tem no workspace, nos dois sentidos:
        associar alguém como `viewer` aqui restringe de fato um `developer` do
        workspace neste projeto. Dois movimentos são recusados com 403 e não
        podem ser habilitados em lugar nenhum: rebaixar um `owner` do workspace,
        e mudar o PRÓPRIO papel — para baixo ou para cima, já que a autopromoção
        é a metade que escala privilégio.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: >-
            Papel insuficiente no projeto, OU um dos dois tetos (o alvo é
            `owner` do workspace; o alvo é quem chama e o papel pedido difere do
            efetivo atual dele).
        '404':
          description: O projeto não existe ou é invisível para quem chama.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Associa um usuário ao projeto
      tags:
        - projects
  /projects/{projectId}/members/{userId}:
    delete:
      description: >-
        Remove só a associação de PROJETO. Quem tem papel no workspace continua
        vendo o projeto por herança — e como o papel de projeto SOBREPÕE o de
        workspace nos dois sentidos, essa herança pode ser MENOR que o que a
        linha concedia. Remover a própria linha é, por isso, recusado com 403
        sempre que o efeito líquido é rebaixamento (inclusive quando não há
        papel de workspace nenhum); removê-la quando o workspace segura o mesmo
        papel, e remover qualquer outra pessoa, continuam funcionando.
      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: Associação removida. Sem corpo.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: >-
            Papel insuficiente no projeto, OU quem chama está removendo a
            própria linha e ficaria com papel menor que o de hoje.
        '404':
          description: O projeto não existe ou é invisível para quem chama.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Desassocia um usuário do projeto
      tags:
        - projects
  /projects/{projectId}/mirror-path:
    put:
      description: >-
        O caminho absoluto, NA MÁQUINA DO USUÁRIO, para onde o agente local
        copia o trabalho (RN-515, ADR 0147) — uma pasta FORA da base montada,
        que é toda a razão de o espelho existir. Por projeto e nunca global: um
        destino global aterrissaria os artefatos do projeto B na pasta do
        projeto A, e o usuário descobriria pelo conteúdo, não por um erro.
        `mirrorPath: null` o LIMPA e desliga o espelho — a chave é obrigatória,
        omiti-la é 400, porque um corpo que omite o campo seria indistinguível
        de pedir para limpar. `null` é o estado NORMAL de um projeto.
        `maintainer`, o mesmo mínimo de `execution-mode` e `projects-base`: esta
        rota fala de um caminho no próprio sistema de arquivos do operador.
        Valida SÓ a forma LÉXICA e recusa, com 400, destino dentro de
        `workspacePath` ou que o contenha (os dois sentidos do mesmo laço), e
        qualquer destino num projeto `container`. Nunca toca o disco: a API não
        enxerga a máquina onde o destino vai morar. Gravar o espelho NÃO é
        proposed_action — é configuração que o usuário declarou, não agente
        pedindo para agir.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: O projeto não existe ou é invisível para quem chama.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Declara (ou limpa) o destino de espelho do projeto
      tags:
        - projects
  /projects/{projectId}/mirror-state:
    get:
      description: >-
        Telemetria que o agente local empurrou pelo canal depois de copiar o
        trabalho para a pasta do usuário (ADR 0147, ponto 7) — nunca o event
        log, porque rodada de espelho não tem sessão e
        `session_events.session_id` é `NOT NULL` (o mesmo raciocínio que fez de
        `rag_searches` uma tabela). TRÊS respostas que nunca colapsam numa só
        (RN-088): `never` (nenhuma rodada relatada), `synced` (a última rodada
        copiou — `filesCopied` pode ser `0`, que quer dizer "olhou e não havia
        nada a copiar") e `failed`. Uma falha nunca apaga a última sincronização
        bem-sucedida, e um sucesso nunca apaga o último erro: qual é o CORRENTE
        sai da comparação dos dois carimbos, então a tela pode dizer "falhando
        desde hoje, a última cópia boa foi ontem com 412 arquivos".
        `lastDestination` é CONGELADO — diverge de `mirrorPath` depois que
        alguém troca o destino, porque a concessão viaja no join e só muda
        quando o runner reconecta (RN-516). `viewer`, o mesmo mínimo de ler o
        projeto, que já traz `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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: O projeto não existe ou é invisível para quem chama.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: O que a última rodada de espelho fez (RN-517)
      tags:
        - projects
  /projects/{projectId}/model-binding:
    get:
      description: O binding CRU deste escopo — `null` se o projeto só herda do workspace.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no escopo.
        '404':
          description: Escopo não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lê o binding de modelo do projeto
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no escopo.
        '404':
          description: Escopo não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Define o modelo do projeto
      tags:
        - llm
  /projects/{projectId}/model-bindings/resolved:
    get:
      description: >-
        Forma em lote de `GET .../agent-bindings/:agentSlug` e `GET
        .../area-bindings/:areaKey`: cada chave pedida recebe EXATAMENTE o valor
        que a rota de chave única dela responde (mesma cascata, mesmas origens,
        `null` quando nenhum nível tem modelo). Existe para a aba Configurações
        ler 20 bindings resolvidos com uma requisição em vez de 20 — o rate
        limit é por USUÁRIO. As chaves são separadas por vírgula, deduplicadas,
        e chave malformada ou mais de 64 chaves no total é 400, nunca descartada
        em silêncio.
      operationId: ModelBindingsController_getResolvedBindings
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: agents
          required: false
          in: query
          description: Slugs de agente separados por vírgula.
          schema:
            example: criativo,po,dev-lead
            type: string
        - name: areas
          required: false
          in: query
          description: Chaves de área separadas por vírgula.
          schema:
            example: dev,qa,infra
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BindingsResolvidosEmLoteResponseDto'
        '400':
          description: Chave malformada, ou mais chaves que o teto do lote.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no escopo.
        '404':
          description: Escopo não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Resolve o modelo de vários agentes e áreas numa leitura só
      tags:
        - llm
  /projects/{projectId}/models:
    get:
      description: >-
        A categoria `local` são os do Ollama, que não gastam dinheiro; `cloud`
        são os de API paga. Modelo que o workspace não ativou não aparece aqui,
        mas fica nos custos históricos — para vê-lo use a rota do catálogo. A
        lista é a do workspace DONO do projeto: o mesmo modelo pode estar ligado
        num workspace e desligado no vizinho (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: Sem token, token expirado ou assinatura inválida.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lista os modelos ativos NO WORKSPACE do projeto
      tags:
        - llm
  /projects/{projectId}/open-questions:
    get:
      description: >-
        O status vem do event log: aberta é um `artifact.open_question` sem
        `open_question.answered`. A mais nova primeiro, teto de 50 com `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: Sem token, token expirado ou assinatura inválida.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lista as perguntas abertas dos agentes do projeto (RN-858)
      tags:
        - backlog
  /projects/{projectId}/open-questions/{questionId}/answer:
    post:
      description: >-
        Só o autor da sessão onde a pergunta nasceu responde. Grava um
        `decision_record` (ator: o usuário, com `questionId`) e
        `open_question.answered`, e pede ao engine para retomar o gate de QA
        estacionado de cada tarefa afetada (RN-860), em melhor esforço.
      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: Sem resposta, ou sugestão aceita onde não há nenhuma.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: >-
            Não é o autor da sessão — corpo com `reason:
            "so_o_autor_da_sessao"`.
        '404':
          description: A pergunta não é deste projeto.
        '409':
          description: 'Já respondida — `reason: "pergunta_ja_respondida"`.'
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Responde uma pergunta aberta (RN-858)
      tags:
        - backlog
  /projects/{projectId}/permissions:
    get:
      description: >-
        É o arquivo físico na raiz do workspace do projeto, não uma coluna do
        banco.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: O projeto não existe ou é invisível para quem chama.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lê o permissions.json do projeto
      tags:
        - projects
    put:
      description: >-
        Substitui as três listas de uma vez. `deny` continua vencendo `allow` na
        avaliação — não há como liberar por aqui algo negado.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: O projeto não existe ou é invisível para quem chama.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Reescreve o permissions.json do projeto
      tags:
        - projects
  /projects/{projectId}/personal-access-tokens:
    get:
      description: Nunca inclui o token bruto — só o que já foi emitido.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lista os próprios Personal Access Tokens deste projeto
      tags:
        - projects
    post:
      description: >-
        O token bruto (`brb_...`) só aparece NESTA resposta — não fica
        recuperável depois, só o hash é guardado. Use em
        `--token`/`BRABO_ACCOUNT_TOKEN` do `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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Emite um Personal Access Token pro runner local
      tags:
        - projects
  /projects/{projectId}/personal-access-tokens/{tokenId}:
    delete:
      description: >-
        Idempotente — revogar de novo não é erro. Desde a RN-685 (ADR 0201)
        também DERRUBA as conexões de runner abertas com ESTE token, e só elas —
        a chave de dispositivo do mesmo usuário fica de pé —, e anula os tickets
        dele ainda não usados. Engine fora do ar NÃO faz a revogação falhar.
      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: Token revogado. Sem corpo.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Revoga um Personal Access Token próprio
      tags:
        - projects
  /projects/{projectId}/personal-access-tokens/{tokenId}/admin:
    delete:
      description: >-
        Resposta a incidente — dev desligado com token vazando (RN-427).
        Idempotente — revogar de novo não é erro. Desde a RN-685 (ADR 0201)
        também DERRUBA o runner já conectado com este token, e só ele.
      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: Token revogado. Sem corpo.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Revoga o Personal Access Token de QUALQUER usuário no projeto
      tags:
        - projects
  /projects/{projectId}/personal-access-tokens/all:
    get:
      description: >-
        Visão de `maintainer` para resposta a incidente (RN-427) — inclui o dono
        de cada token. Nunca inclui o token bruto.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lista TODOS os Personal Access Tokens do projeto, de qualquer usuário
      tags:
        - projects
  /projects/{projectId}/proficiency:
    get:
      description: >-
        O PRÓPRIO perfil do usuário por padrão; a visão agregada do time só para
        quem administra o projeto. Perfil de competência é dado SOBRE a pessoa,
        e o padrão menos surpreendente é ver o seu.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, evento ou versão não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Devolve o perfil de proficiência
      tags:
        - anamnesis
  /projects/{projectId}/proficiency/me:
    delete:
      description: >-
        Só o PRÓPRIO perfil de quem chama, nunca o de outra pessoa. O opt-out
        vem junto porque sem ele a próxima rodada re-derivaria tudo e a exclusão
        seria cosmética. É `viewer` de propósito: a perfilagem cobre todos os
        membros, então exigir `developer` deixaria um viewer perfilado sem
        conseguir apagar o próprio.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, evento ou versão não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Apaga o próprio perfil e grava o opt-out
      tags:
        - anamnesis
  /projects/{projectId}/proficiency/me/opt-in:
    post:
      description: >-
        Desfaz o opt-out. Os perfis voltam a ser derivados a partir da próxima
        rodada.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, evento ou versão não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Reativa a perfilagem do próprio usuário
      tags:
        - anamnesis
  /projects/{projectId}/psychologist/analyses:
    get:
      description: >-
        `costMicros` é somado do `token_usage` da rodada. É isso que torna
        VISÍVEL a diferença de custo entre triagem leve e pesada, em vez de só
        afirmada — as duas faixas usam modelos de fato diferentes.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou hipótese não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lista as rodadas de análise com faixa e custo real
      tags:
        - psychologist
  /projects/{projectId}/psychologist/status:
    get:
      description: >-
        Somente leitura — não tem efeito colateral. `enabled: false` quer dizer
        que a pausa é decisão de PRODUTO (não bug): análises e hipóteses
        existentes não são tocadas por ela.
      operationId: PsychologistController_status
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PsychologistStatusResponseDto'
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou hipótese não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Informa se o Psicólogo pode rodar uma análise NOVA hoje
      tags:
        - psychologist
  /projects/{projectId}/rag/coverage:
    get:
      description: >-
        Contagem real (nunca estimada) de arquivos `.md` sob `docs`/`docs/adr`
        no repositório do projeto contra quantos têm chunk, e das sessões do
        projeto contra quantas têm chunk. Não inclui "há N minutos" para esses
        três escopos — não há coluna de carimbo de indexação por escopo, e um
        número chutado mentiria (mesma régua do ADR 0042 para nota de modelo).
        `local` (RN-455) é a única exceção: `lastAttachedAt` é um
        `MAX(created_at)` real sobre aquele escopo, porque upload pelo navegador
        não tem estado de repositório contra o qual comparar.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: 'Cobertura do índice: arquivos/sessões indexados contra o total real'
      tags:
        - rag
  /projects/{projectId}/rag/feedback:
    post:
      description: >-
        O único sinal de VERDADE que a medição do RAG tem: latência e taxa de
        degradação dizem se a busca RODOU, nunca se ACERTOU. Mesmo papel de
        `search` — quem pode ler o resultado é quem pode julgá-lo. Votar de novo
        no mesmo chunk da mesma busca sobrescreve o próprio voto em vez de somar
        um segundo (único por ator), para a métrica medir acerto e não
        entusiasmo.
      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` desconhecido para este projeto, ou um `chunkId` que não
            estava entre os acertos daquela busca — voto sem posição seria
            número sem significado.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Julga um acerto de uma busca como útil ou irrelevante (RN-480)
      tags:
        - rag
  /projects/{projectId}/rag/local:
    post:
      description: >-
        Conteúdo de texto já lido pelo navegador (`<input webkitdirectory>`),
        nunca um caminho do host — essa distinção é todo o ponto do ADR 0113.
        Reconstrução completa e idempotente do escopo `local`: toda chamada o
        apaga e recria a partir dos arquivos enviados DESTA vez, então reenviar
        é o mecanismo de ressincronização. Recusa (400) acima dos tetos de
        quantidade de arquivos/bytes totais em vez de cortar em silêncio — é um
        gesto único do usuário com um seletor de pasta à frente, não uma
        varredura em segundo plano. Arquivo individual grande demais ou de
        extensão não reconhecida é só pulado, nunca reprova o lote.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: >-
        Anexa uma pasta local (upload pelo navegador) como referência somente
        leitura (RN-455, ADR 0113)
      tags:
        - rag
  /projects/{projectId}/rag/reindex:
    post:
      description: >-
        "Reindexar agora" do painel de RAG do Chat. Apaga e recria os chunks dos
        três escopos honestos (RN-219) a partir do estado CORRENTE — não há
        watcher automático em push/fechamento de sessão (decisão registrada nos
        ADRs 0079/0080). Pode demorar em projetos com muitas sessões: roda uma
        passada de indexação por sessão, cada uma possivelmente chamando o
        provider de embedding em lote.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: >-
        Reindexa docs/ADRs/sessões do projeto (reconstrução completa
        idempotente)
      tags:
        - rag
  /projects/{projectId}/rag/search:
    post:
      description: >-
        Combina similaridade de cosseno (pgvector) com `ts_rank` léxico
        (tsvector), cada um de uma consulta independente, mesclados por soma
        ponderada e cortados pelo limiar (ADR 0080). `vectorAvailable: false`
        avisa quando o provider de embedding não respondeu e a busca rodou só
        com o sinal léxico — nunca finge ter rodado o híbrido completo.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Busca híbrida (vetorial + léxica) sobre os chunks indexados do projeto
      tags:
        - rag
  /projects/{projectId}/run-instructions:
    get:
      description: >-
        RN-852 (ADR 0213): o `artifact.run_instructions` vigente, emitido quando
        um `open_infra_pr` com `run` executa. DECLARADO pelo agente de Infra,
        nunca verificado na máquina de quem lê. `null` quando não há.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Devolve como rodar o entregável na sua máquina
      tags:
        - backlog
  /projects/{projectId}/runner-device-keys:
    get:
      description: >-
        Ninguém revoga o que não consegue ver (RN-519). Inclui as já REVOGADAS —
        sumir com a linha faria a tela afirmar que a chave nunca existiu. Nunca
        devolve a JWK pública, e a privada a api nunca viu. `lastUsedAt` nulo é
        o sinal de uma chave ÓRFÃ: registrada e nunca usada por runner nenhum.
        Desde a RN-543 inclui também as chaves de MÁQUINA do chamador (`especie:
        "maquina"`, `projectId` nulo), que servem este projeto sem pertencer a
        ele — sem elas na lista, uma chave de máquina seria invisível em toda
        tela.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lista as próprias chaves de dispositivo deste projeto
      tags:
        - projects
  /projects/{projectId}/runner-device-keys/{deviceKeyId}:
    delete:
      description: >-
        Idempotente — revogar de novo não é erro. Desde a RN-520 também DERRUBA
        a conexão viva: antes, revogar só impedia ticket NOVO, e um runner já
        conectado seguia executando comando aprovado. Desde a RN-685 (ADR 0201)
        o alvo é a CHAVE: caem só as conexões abertas com ela — em todo projeto,
        se for de máquina —, e outro runner do mesmo usuário, conectado com PAT
        ou com outra chave, fica de pé. Os tickets dela ainda não usados são
        anulados. Engine fora do ar ou nenhum runner conectado NÃO fazem a
        revogação falhar.
      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: Chave revogada. Sem corpo.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Revoga uma chave de dispositivo própria
      tags:
        - projects
  /projects/{projectId}/runner-ticket:
    post:
      description: >-
        Autentica `connect/3` do socket Phoenix `/runner` (tópico
        `terminal:<projectId>`, papel "runner") — NÃO é o JWT reaproveitado, e
        esta rota em si só aceita um Personal Access Token no lugar do JWT de
        sessão (ADR 0105). TTL de 30s e uso único. Recusa com 400 se o projeto
        não estiver no modo "runner" (RN-421): o runner não tem o que servir num
        projeto cujo código mora no container gerenciado.
      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: Projeto não está no modo "runner".
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Emite um ticket de uso único para o runner local conectar
      tags:
        - projects
  /projects/{projectId}/sessions:
    get:
      description: >-
        Cada item traz `technical`: `true` só para a sessão aberta pelo
        provisionamento do repositório (ligada por `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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto ou sessão não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lista as sessões do projeto
      tags:
        - sessions
    post:
      description: >-
        A sessão nasce em `created` e é o contêiner de tudo o que os agentes
        fazem: o event log, as ações propostas e o orçamento de tokens ficam
        pendurados nela. `kind` é OBRIGATÓRIO e fica gravado: `consultiva` só
        conversa, `criativa` produz e é a única que aceita
        `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: Corpo sem `kind`, ou com valor fora da lista permitida.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto ou sessão não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Abre uma sessão no projeto
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto ou sessão não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Devolve uma sessão pelo id
      tags:
        - sessions
    patch:
      description: >-
        O nome é rótulo de navegação e NÃO substitui a hashtag do id — as telas
        compõem os dois, e sem nome degradam para só a hashtag. `null` ou texto
        em branco limpa o nome. Não há como mudar o `kind`: é a intenção de
        criação, e mudá-lo depois o transformaria em estado.
      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: Corpo sem `name`, ou nome acima do limite de caracteres.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto ou sessão não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Define ou limpa o nome amigável da sessão
      tags:
        - sessions
  /projects/{projectId}/sessions/{sessionId}/actions:
    get:
      description: >-
        Ordenadas por `seq`; use `nextCursor` como `afterSeq`. `latest=true`
        busca a CAUDA em vez disso (as `limit` ações mais novas, ainda em ordem
        crescente) e ignora `afterSeq`. `status=pending` mantém só as ações
        esperando decisão — com `latest`, as pendentes mais novas, para que uma
        sessão com mais de 200 ações não empurre uma pendente nova para fora da
        janela (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: Busca a cauda da sessão; ignora `afterSeq`.
          schema:
            example: 'true'
            type: string
        - name: status
          required: false
          in: query
          description: Só `pending` é suportado.
          schema:
            example: pending
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaginaDeAcoesResponseDto'
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou ação não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Pagina as ações propostas da sessão
      tags:
        - actions
    post:
      description: >-
        A ação é avaliada contra o `permissions.json` do projeto na criação. O
        `resolvedPolicy` da resposta diz o que a política decidiu:
        `auto_approve` já sai executável, `require_approval` espera decisão
        humana, e `deny` nasce recusada. Não há caminho que execute sem passar
        por aqui.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou ação não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Propõe uma ação com efeito externo
      tags:
        - actions
  /projects/{projectId}/sessions/{sessionId}/actions/{actionId}/approve:
    post:
      description: >-
        Vale só para esta ação. A decisão fica no event log com o id de quem
        decidiu.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou ação não encontrado.
        '409':
          description: A ação já foi decidida ou executada.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Aprova a ação, uma vez
      tags:
        - actions
  /projects/{projectId}/sessions/{sessionId}/actions/{actionId}/approve_always:
    post:
      description: >-
        Além de liberar esta ação, acrescenta o padrão correspondente à lista
        `allow` do projeto — ações futuras que casem saem `auto_approved` sem
        perguntar. Padrão já em `deny` continua bloqueado. A aprovação e o
        padrão são gravados na mesma transação. Clicar numa ação já APROVADA é
        sucesso idempotente (`desfecho: ja_aprovada`), gravando o padrão só se
        ele faltar.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou ação não encontrado.
        '409':
          description: >-
            A ação já foi RECUSADA (`reason: acao_ja_recusada`); nenhum padrão é
            gravado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Aprova a ação e grava o padrão no permissions.json
      tags:
        - actions
  /projects/{projectId}/sessions/{sessionId}/actions/{actionId}/deny:
    post:
      description: O motivo, se dado, fica no event log junto com a decisão.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou ação não encontrado.
        '409':
          description: A ação já foi decidida ou executada.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Recusa a ação
      tags:
        - actions
  /projects/{projectId}/sessions/{sessionId}/agents/{agent}/cancel:
    post:
      description: >-
        O botão "Parar" do composer (RN-122): mata a chamada de LLM em voo no
        engine, cortando a conexão no meio do stream — economiza tokens de
        verdade, não só para de renderizar no cliente. Idempotente: sem turno em
        andamento, é aceito sem efeito.
      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 do agente ativo.
          schema:
            example: po
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OkResponseDto'
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou handoff não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Cancela o turno em andamento do agente ativo
      tags:
        - agents
  /projects/{projectId}/sessions/{sessionId}/agents/{agent}/message:
    post:
      description: >-
        A resposta é só a confirmação, e volta no ACEITE — antes de o turno do
        agente terminar (ADR 0163). O que o agente responde chega pelo event log
        e pelo canal da sessão — não por esta chamada. Se o agente está no meio
        de um turno, a mensagem NÃO é recusada: entra na fila do agente
        (`entrega: "enfileirada"`) e é lida, com as outras enfileiradas, num
        turno só quando o atual terminar; pode ser cancelada enquanto espera
        (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 do agente conversacional que lê a mensagem: criativo, po,
            arquiteto, dev-lead, ux-designer ou staff. Qualquer outro slug —
            infra inclusive — é recusado com 422; nunca é entregue a um agente
            padrão (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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou handoff não encontrado.
        '409':
          description: >-
            O agente não está ativo nesta sessão; ou está esperando decisão
            sobre o plano de execução; ou a fila dele já tem 10 mensagens
            (`fila_de_mensagens_cheia`) — a mensagem foi gravada mas NÃO lida
            pelo agente (ADR 0163, RN-673).
        '422':
          description: >-
            O agente não recebe mensagens de chat (o Infra Lead trabalha por
            proposta, e todo slug sem cláusula própria no engine é recusado pelo
            nome) — a mensagem foi gravada mas NENHUM agente a leu (RN-584).
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Envia uma mensagem ao agente ativo
      tags:
        - agents
  /projects/{projectId}/sessions/{sessionId}/agents/{agent}/messages/{messageId}/cancel:
    post:
      description: >-
        Mensagem enviada com o agente no meio de um turno espera numa fila e é
        lida quando o turno termina (RN-673). Enquanto espera, quem a ENVIOU
        pode cancelá-la: o engine a tira da fila e grava
        `chat.message_cancelled`; ela nunca é mostrada ao modelo. Mensagem de
        outra pessoa é 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 do agente em cuja fila está a mensagem.
          schema:
            example: po
            type: string
        - name: messageId
          required: true
          in: path
          description: >-
            Id do evento `chat.message` (o `mensagemId` que a rota de mensagem
            devolveu).
          schema:
            example: 01JC4Z0000EVENTO000000000001
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OkResponseDto'
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou handoff não encontrado.
        '409':
          description: >-
            A mensagem não está mais na fila — o agente já a leu, ou ela já foi
            cancelada; ou a sessão está encerrada (RN-581).
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Cancela uma mensagem que espera na fila do agente
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel abaixo de `developer` no projeto.
        '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: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Resumes the turn an engine restart interrupted
      tags:
        - sessions
  /projects/{projectId}/sessions/{sessionId}/agents/{agent}/start:
    post:
      description: >-
        Só o Criativo pode ser iniciado por comando direto. Todo outro agente
        exige um handoff ACEITO apontando para ele — a regra mora no domínio,
        não aqui, e tentar contorná-la devolve 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 do agente.
          schema:
            example: criativo
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgenteAtivadoResponseDto'
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou handoff não encontrado.
        '409':
          description: Agente sem handoff aceito, ou já ativo na sessão.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Inicia um agente na sessão
      tags:
        - agents
  /projects/{projectId}/sessions/{sessionId}/agents/{agent}/structured-question/{questionSetId}/answer:
    post:
      description: >-
        Grava `chat.structured_question_answered` e reenvia as respostas ao
        agente como mensagem normal. Um conjunto de perguntas só pode ser
        respondido uma vez. Volta no ACEITE, antes de o turno do agente terminar
        (ADR 0163): o turno segue rodando no engine e a narração, o fim e as
        falhas dele chegam pelo canal da sessão e pelo event log
        (`agent.status`, `agent.response`, `agent.error`) — nunca por esta
        resposta.
      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 do agente que fez as perguntas (e vai ler a resposta).
          schema:
            example: criativo
            type: string
        - name: questionSetId
          required: true
          in: path
          description: Id do evento `chat.structured_question` respondido.
          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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou handoff não encontrado.
        '409':
          description: >-
            Este conjunto de perguntas já foi respondido; ou o agente ainda está
            no meio de um turno e não leu as respostas (ADR 0163).
        '422':
          description: >-
            O agente que perguntou não recebe mensagens de chat — as respostas
            foram gravadas mas NENHUM agente as leu (RN-584).
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Responde um conjunto de perguntas estruturadas do agente
      tags:
        - agents
  /projects/{projectId}/sessions/{sessionId}/agents/{agentId}/rearm:
    post:
      description: >-
        Zera o contador de tarefas bloqueadas consecutivas (RN-047) e devolve o
        agente a tentar reivindicar trabalho. É a única saída de `idle_tripped`
        — não há desbloqueio automático.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou tarefa não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Rearma um dev agent desarmado pelo circuit breaker
      tags:
        - execution
  /projects/{projectId}/sessions/{sessionId}/agents/arquiteto/handoff-infra:
    post:
      description: >-
        Endpoint dedicado em vez de reusar `readiness`, que é do Criativo: são
        dois marcos diferentes da sessão, e confundi-los tornaria o event log
        ambíguo. Volta no ACEITE, antes de o turno do agente terminar (ADR
        0163): o turno segue rodando no engine e a narração, o fim e as falhas
        dele chegam pelo canal da sessão e pelo event log (`agent.status`,
        `agent.response`, `agent.error`) — nunca por esta resposta. O handoff ao
        Dev Lead continua vindo DEPOIS do de Infra: o engine o segura até o
        turno de fechamento terminar. Idempotente (ADR 0182, RN-635): alvo que
        já tem oferta pendente ou está ativo no projeto não é disparado de novo,
        e com os dois assim nada é gravado.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou handoff não encontrado.
        '409':
          description: >-
            O Arquiteto ainda está no meio de um turno — a confirmação foi
            gravada mas o turno de fechamento não começou (ADR 0163).
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Confirma que a arquitetura está pronta e oferece o handoff à Infra
      tags:
        - agents
  /projects/{projectId}/sessions/{sessionId}/agents/criativo/validate-necessity:
    post:
      description: >-
        Grava `necessity.validated`. Exige que o Criativo já tenha consolidado
        um `product_brief` nesta sessão (RN-406) — sem ele, é recusado: não há o
        que validar. Desde o ADR 0185 (RN-657) o clique "Estou pronto — a
        necessidade está validada" (`POST .../readiness`) grava ele mesmo este
        evento, e a web não chama mais esta rota; ela fica para sessões cujo
        clique de prontidão é anterior a esse 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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou handoff não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Confirma que a necessidade de negócio do Criativo foi validada
      tags:
        - agents
  /projects/{projectId}/sessions/{sessionId}/budget:
    get:
      description: É isto que alimenta o medidor de tokens ao vivo da tela de sessão.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto ou sessão não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lê o orçamento da sessão e o gasto acumulado
      tags:
        - llm
    put:
      description: Mesma conversão de dólar para micro-USD do orçamento do projeto.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto ou sessão não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Define o teto de gasto da sessão
      tags:
        - llm
  /projects/{projectId}/sessions/{sessionId}/chat:
    post:
      description: >-
        Server-Sent Events. Quadros `delta` levam o texto incremental e `done`
        fecha com a contabilidade de tokens e custo. Um quadro `metering_failed`
        quer dizer que a RESPOSTA saiu mas o custo não foi contabilizado — a
        falha aparece em vez de sumir. Se o orçamento estourar com
        `policy=block`, o stream leva `error` e nenhum `delta`. Esta rota NÃO
        leva agente: manda o texto sozinho ao modelo vinculado, sem histórico e
        sem prompt de sistema. Numa sessão `consultiva` onde nenhum agente foi
        ativado ela é recusada antes de qualquer efeito (RN-682) — converse com
        um agente por `.../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: Fluxo de quadros até `done` ou `error`.
          content:
            text/event-stream:
              schema:
                $ref: '#/components/schemas/ChatSseEventResponseDto'
        '400':
          description: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto ou sessão não encontrado.
        '422':
          description: >-
            `destinatario_ausente`: sessão `consultiva` sem agente — a mensagem
            não tem destinatário. Nada foi gravado e nenhum modelo foi chamado
            (RN-682).
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Conversa com o modelo da sessão, com a resposta em streaming
      tags:
        - llm
  /projects/{projectId}/sessions/{sessionId}/context-summary:
    get:
      description: >-
        Regras de negócio, registros de decisão e artefatos por agente contados
        sobre o log INTEIRO da sessão (a tela lê uma cauda de 200 eventos), mais
        os arquivos tocados por `git_commit`/`git_push` nesta sessão e na sessão
        de execução que o plano aprovado do Dev Lead dela ativou (RN-828). Desde
        a RN-844 devolve também os ITENS de regras e decisões (os 50 mais
        recentes de cada; os totais ficam em `regras`/`decisoes`) e os arquivos
        das PRs abertas nessas sessões, lidos do mesmo diff que a aba de PRs lê,
        para no máximo 20 PRs por chamada; `prsNaoLidas` diz quantas ficaram de
        fora ou falharam. Só leitura; nenhuma tabela nova.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto ou sessão não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Conta a sessão inteira para o painel de contexto
      tags:
        - sessions
  /projects/{projectId}/sessions/{sessionId}/events:
    get:
      description: >-
        O log é IMUTÁVEL e ordenado por `seq`. A paginação normal é incremental:
        passe o `nextCursor` da resposta anterior como `afterSeq`. `latest=true`
        faz o contrário — busca a CAUDA e ignora `afterSeq`, para uma tela que
        abre no fim em vez de reconstruir a sessão inteira desde o começo (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: Devolve os eventos com `seq` maior que este.
          schema:
            example: 40
            type: string
        - name: limit
          required: false
          in: query
          schema:
            example: 50
            type: string
        - name: latest
          required: false
          in: query
          description: Busca a cauda do log; ignora `afterSeq`.
          schema:
            example: 'true'
            type: string
        - name: actionId
          required: false
          in: query
          description: >-
            Só os eventos cujo `payload.actionId` é esta ação — ex.: o
            `proposed_action.created` dela, que traz o `reason` da política e o
            `scopeRoot` que a linha da ação não guarda (RN-614). Combina com os
            outros parâmetros; o `seq` deixa de ser contíguo na página, então
            quem filtra por ação não deve derivar dele contagem de omitidos.
            Valor vazio é `400`.
          schema:
            example: 01JC4Z0000ACAO00000000000001
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaginaDeEventosResponseDto'
        '400':
          description: '`actionId` está presente mas vazio.'
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto ou sessão não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Pagina o event log da sessão
      tags:
        - sessions
    post:
      description: >-
        Append-only: o `seq` é atribuído pelo servidor e nada no log é
        atualizado ou removido depois. Não há endpoint de edição, por desenho.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto ou sessão não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Acrescenta um evento ao log da sessão
      tags:
        - sessions
  /projects/{projectId}/sessions/{sessionId}/events/{eventId}:
    get:
      description: >-
        Existe para que a evidência das hipóteses do Psicólogo alcance o evento
        citado não importa em que página ele caiu nem os filtros do feed.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto ou sessão não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Devolve um evento do log pelo id
      tags:
        - sessions
  /projects/{projectId}/sessions/{sessionId}/execution/parallelize:
    post:
      description: >-
        Passa pelo TETO da área de dev (RN-083): abaixo dele, o agente sobe na
        hora, no próprio worktree; acima, a resposta é uma `proposed_action` do
        tipo `parallelize` aguardando a sua decisão, e NADA sobe até você
        decidir. O valor do teto é configurável por lead em Configurações.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou tarefa não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Pede mais um dev agent para um módulo
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou handoff não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lista os handoffs entre agentes da sessão
      tags:
        - agents
    post:
      description: >-
        Nasce `offered`, exatamente como o `offer_handoff` de um agente — a
        única diferença é quem decidiu. `toAgent` tem de estar no catálogo
        endereçável (lead de área ou agente sem área); subagente ou slug
        desconhecido é recusado com 400. No máximo uma oferta pendente por
        (projeto, alvo): ver `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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou handoff não encontrado.
        '409':
          description: >-
            `agente_ja_ativo` (ADR 0182, RN-635): o alvo já está ativo numa
            sessão não encerrada do projeto — nenhuma oferta é criada.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Oferece manualmente um handoff a um agente escolhido
      tags:
        - agents
  /projects/{projectId}/sessions/{sessionId}/handoffs/{handoffId}/accept:
    post:
      description: >-
        Uma coisa só: transicionar para `accepted` E iniciar o agente de
        destino. É o caminho normal de ativação de todo agente que não é o
        Criativo.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou handoff não encontrado.
        '409':
          description: O handoff não está em `offered`.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Aceita um handoff e ativa o agente de destino
      tags:
        - agents
  /projects/{projectId}/sessions/{sessionId}/model-binding:
    get:
      description: >-
        Devolve o binding RESOLVIDO pela cascata, não o binding cru da sessão:
        sessão sem binding próprio usa o do projeto, e responder `null` aqui
        seria a resposta errada para a pergunta certa. O campo `origin` diz de
        qual escopo veio o valor.


        `agentId` é opcional e é o agente REALMENTE ativo na sessão agora (o
        mesmo que `RunLlmTurnUseCase` usa para rodar o turno) — sem ele, a
        cascata só enxerga sessão→projeto→workspace (mais o fallback fixo ao
        Criativo) e nunca reflete PO/Arquiteto/Dev Lead/área depois de um
        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: >-
            Agente ativo agora nesta sessão (ex.: "po", "arquiteto",
            "dev-lead").
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResolvedBindingResponseDto'
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no escopo.
        '404':
          description: Escopo não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Resolve qual modelo a sessão usa, e de onde veio
      tags:
        - llm
    put:
      description: Vence todo o resto da cascata enquanto a sessão viver.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no escopo.
        '404':
          description: Escopo não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Fixa o modelo desta sessão
      tags:
        - llm
  /projects/{projectId}/sessions/{sessionId}/psychologist/reanalyze:
    post:
      description: >-
        Exige `maintainer`, não `developer`, porque roda o ToolLoop de novo e
        GASTA orçamento real — ao contrário de aceitar ou descartar, que só
        mexem em estado local. A análise anterior é marcada como superada.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou hipótese não existe.
        '429':
          description: Rate limit por usuário ou por IP.
        '503':
          description: >-
            O Psicólogo está desligado globalmente por decisão do usuário (não é
            bug) — corpo com `reason: "psychologist_disabled"`.
      security:
        - bearer: []
      summary: Dispara uma reanálise da sessão
      tags:
        - psychologist
  /projects/{projectId}/sessions/{sessionId}/readiness:
    post:
      description: >-
        É o botão que dispara o `product_brief` e o handoff ao PO. Grava
        `readiness.confirmed` no event log. Volta no ACEITE, antes de o turno do
        agente terminar (ADR 0163): o turno segue rodando no engine e a
        narração, o fim e as falhas dele chegam pelo canal da sessão e pelo
        event log (`agent.status`, `agent.response`, `agent.error`) — nunca por
        esta resposta.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou handoff não encontrado.
        '409':
          description: >-
            O Criativo ainda está no meio de um turno — a confirmação foi
            gravada mas o brief não começou (ADR 0163).
        '422':
          description: >-
            Nenhuma regra de negócio foi capturada nesta conversa — ainda não há
            o que consolidar num brief (ADR 0163).
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Confirma que a sessão de descoberta com o Criativo terminou
      tags:
        - agents
  /projects/{projectId}/sessions/{sessionId}/reopen:
    post:
      description: >-
        Só `closed`/`closed_abnormally` reabrem, e só para `active` — `closing`
        nunca volta (ADR 0183). A sessão mantém o event log, os artefatos, as
        perguntas respondidas e os handoffs; o `kind` não é tocado. Um evento
        NOVO `session.reopened` registra o `closedAt` e o `terminationReason`
        anteriores, que a linha limpa. Sessão que traz `execution.activated` é
        recusada (`sessao_com_execucao`): abra uma sessão nova e ative a
        execução nela. Sem prazo. Exige `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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto ou sessão não existe.
        '409':
          description: >-
            A sessão não está encerrada (`reason: sessao_nao_encerrada`), ou
            ativou execução (`reason: sessao_com_execucao`).
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Reabre uma sessão encerrada, mantendo tudo o que ela tinha
      tags:
        - sessions
  /projects/{projectId}/sessions/{sessionId}/response-language:
    get:
      description: >-
        Resolvido para QUEM CHAMA, nunca para a sessão: sobreposição da sessão >
        escolha da Conta > detectado-e-confirmado > idioma da interface
        (RN-618). Devolve o vencedor, a origem dele e cada elo da cadeia — e,
        quando as suas mensagens recentes apontam para outro idioma e a tela
        deveria perguntar sobre isso, `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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto ou sessão não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: O idioma em que os agentes respondem a VOCÊ, nesta sessão
      tags:
        - sessions
    put:
      description: >-
        Só para quem chama, só nesta sessão (RN-618) — os outros participantes e
        a sua escolha na Conta não são tocados. `null` volta a herdar da Conta.
      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: >-
            Corpo sem `language`, ou código que não é um idioma BCP-47
            reconhecido.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto ou sessão não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Fixa (ou solta) o SEU idioma de resposta nesta sessão
      tags:
        - sessions
  /projects/{projectId}/sessions/{sessionId}/socket-ticket:
    post:
      description: >-
        O ticket autentica o socket Phoenix `session:<id>` da sessão, no
        `connect/3` do engine — NÃO é o JWT reaproveitado. TTL de 30s e uso
        único: toda reconexão (inclusive as automáticas) pede um ticket novo.
        `scope: "heartbeat"` exige o papel `viewer`; `scope: "terminal"` exige
        `developer`, o mesmo papel mínimo das ações de terminal.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente para o escopo pedido.
        '404':
          description: Projeto ou sessão não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Emite um ticket opaco de uso único para o socket da sessão
      tags:
        - sessions
  /projects/{projectId}/sessions/{sessionId}/tasks/{taskId}/unblock:
    post:
      description: >-
        Zera o contador de correções do gate e devolve a tarefa ao agente. É a
        saída humana do teto de correções — não há desbloqueio automático.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto, sessão ou tarefa não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Desbloqueia uma tarefa bloqueada
      tags:
        - execution
  /projects/{projectId}/sessions/{sessionId}/token-usage:
    get:
      description: >-
        O dado sempre esteve em `token_usage`, mas sem agregação nem rota o
        painel do time não tinha como mostrar o custo por card. Mesmo papel
        exigido pelo orçamento da sessão.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto ou sessão não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Quebra o gasto da sessão por agente
      tags:
        - llm
  /projects/{projectId}/sessions/{sessionId}/transition:
    post:
      description: >-
        A transição é validada pela máquina de estados do domínio, não pelo
        cliente: created → active → closing → closed | closed_abnormally. Salto
        inválido é 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: >-
            Devolve 201, não 200, porque o handler é `@Post` sem `@HttpCode` — é
            o padrão do Nest. Documentado como está em vez de mudado: a
            semântica é discutível, o comportamento não é defeituoso, e o teste
            da tabela reprova qualquer divergência entre os dois.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionResponseDto'
        '400':
          description: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto ou sessão não existe.
        '409':
          description: Transição não permitida a partir do estado atual.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Move a sessão para outro estado
      tags:
        - sessions
  /projects/{projectId}/spend/me:
    get:
      description: >-
        O público do MEMBRO. Só as linhas do próprio chamador — o ator vem do
        token autenticado e não há parâmetro para trocá-lo. Em tokens e custo
        ESTIMADO, sem quebrar por provider nem por credencial: a chave que rodou
        é a do owner (RN-058) e a fatura dela é dele (RN-060). O eixo de
        provider passou a existir no relatório do owner (ADR 0076) e continua
        inalcançável daqui — não há parâmetro de dimensão nesta rota, e o tipo
        do repositório recusa a combinação (RN-187). Agente não conta:
        `token_usage` registra quem gastou, não quem mandou gastar.
      operationId: SpendController_getMySpend
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: dias
          required: false
          in: query
          description: Janela deslizante. Padrão 30, máximo 180.
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MySpendResponseDto'
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Meu próprio consumo neste projeto, por sessão e por dia
      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: Janela deslizante. Padrão 30, máximo 180.
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectSpendTeamResponseDto'
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: The project's total spend and cost by agent and area
      tags:
        - llm
  /projects/{projectId}/stories/{storyId}/archive:
    post:
      description: >-
        A história sai do backlog, da cobertura, da fila de promoção, do plano
        do Dev Lead e do claim — e as tarefas dela também. Nada é apagado: a
        linha fica com `archivedAt`, e `backlog.story_archived` registra quem,
        por quê e quais tarefas. Só história `draft` sem tarefa em execução.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não existe.
        '409':
          description: >-
            Recusa nomeada (`reason`): `historia_arquivada`,
            `historia_nao_draft` ou `historia_com_tarefa_em_execucao`.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Arquiva uma história em rascunho (RN-727)
      tags:
        - backlog
  /projects/{projectId}/stories/{storyId}/return:
    post:
      description: >-
        A história sai da fila de proposta e o motivo vira uma mensagem fixada
        na sessão do PO — o mesmo padrão de devolver um gate ao dev. Se o PO
        daquela sessão não está mais de pé, a devolução é gravada mesmo assim: é
        decisão do usuário e não depende de um agente ouvindo.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Devolve uma história recusada ao PO, com o motivo
      tags:
        - backlog
  /projects/{projectId}/stories/{storyId}/title:
    patch:
      description: >-
        Só história `draft` sem tarefa em execução (`in_progress`/`in_review`).
        Grava `backlog.story_updated` com o título anterior e o novo. Mesmo
        papel das outras escritas do backlog (promover/devolver).
      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: Título vazio ou com mais de 200 caracteres.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não existe.
        '409':
          description: >-
            Recusa nomeada (`reason`): `historia_arquivada`,
            `historia_nao_draft` ou `historia_com_tarefa_em_execucao`.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Corrige o título de uma história em rascunho (RN-727)
      tags:
        - backlog
  /projects/{projectId}/stories/promote:
    post:
      description: >-
        O passo humano que o modo `manual` (padrão desde a Fase 12c) devolve ao
        usuário: até a promoção, NENHUMA tarefa da história é pegável por dev
        agent algum. Promover libera o lote de tarefas de uma vez e acorda os
        agentes ociosos do módulo (Fase 12b). O lote NÃO é tudo ou nada — o que
        não passou volta como `failed` com o motivo, e o que passou é promovido.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Promove para `ready` histórias que o PO propôs
      tags:
        - backlog
  /projects/{projectId}/tasks/{taskId}/gates/{gate}/resume:
    post:
      description: >-
        Ciclo de gate parado há mais de 2 h é estacionado em vez de reiniciado
        sozinho (ADR 0207). Este é o gesto humano que o retoma.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel abaixo de `developer` no projeto.
        '409':
          description: >-
            O ciclo não está estacionado — corpo com `reason:
            "gate_nao_estacionado"`.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Retoma um ciclo de gate estacionado
      tags:
        - gates
  /projects/{projectId}/terminal-ticket:
    post:
      description: >-
        Mesmo socket `/runner`, mesmo tópico, papel "terminal" — quem VÊ o
        terminal (o comando em si só roda de verdade se houver um runner
        conectado). Vale para qualquer modo de projeto.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no projeto.
        '404':
          description: Projeto não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Emite um ticket de uso único para a aba Terminal da web
      tags:
        - projects
  /runner-releases/binary:
    get:
      description: >-
        Proxy de GitHub Releases — `platform` aceita só linux-x64, linux-arm64,
        darwin-arm64. Sem autenticação: o binário não é segredo. Os bytes são
        conferidos contra o `checksums.txt` da mesma Release antes de qualquer
        coisa ser respondida (ADR 0149, RN-525); a rota NÃO verifica a
        assinatura do manifesto — isso é integridade, não procedência, e quem
        verifica assinatura é o `install.sh`.
      operationId: RunnerReleasesController_binary
      parameters: []
      responses:
        '200':
          description: >-
            O binário do runner, em stream, com o sha256 já conferido contra o
            manifesto. Não é JSON: `Content-Type: application/octet-stream`. O
            header `Content-Digest` repete o hash conferido (RFC 9530).
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        '502':
          description: >-
            Recusa nomeada — nunca bytes com aviso. O corpo traz `motivo`:
            `plataforma_nao_publicada`, `release_sem_manifesto`,
            `manifesto_nao_cobre_a_plataforma`, `manifesto_ilegivel`,
            `download_falhou` ou `hash_divergente`.
      summary: Baixa o binário standalone do runner local pra plataforma pedida
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Credential bound to a project.
        '409':
          description: Code `credencial_existente`.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Stores an LLM API key imported by the local runner
      tags:
        - credentials
  /runner/projects:
    get:
      description: >-
        Autenticada por uma chave de dispositivo de MÁQUINA (ADR 0154) — NUNCA
        por JWT de sessão, e nunca por uma credencial presa a um projeto, que
        responde 403. Devolve os projetos em `execution_mode: "runner"` nos
        quais o dono da chave alcança pelo menos `developer` (o mesmo mínimo de
        `POST .../runner-ticket`), com o nome da pasta e o estado de verificação
        de cada um. O agente pergunta em vez de varrer o disco: a base da
        máquina é do usuário e pode ter pasta que não é projeto nenhum.
      operationId: RunnerProjectsController_listRunnerProjects
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/RunnerProjectResponseDto'
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: >-
            A credencial apresentada está presa a um projeto — esta rota exige
            uma chave de dispositivo de máquina.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lista os projetos em modo "runner" que este agente local atende
      tags:
        - projects
  /users/me/credentials:
    get:
      description: >-
        Nenhum papel exigido: credencial é sobre quem chamou, não sobre um
        workspace ou projeto. Cobre chaves de LLM e tokens de git na mesma
        listagem.
      operationId: CredentialsController_list
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/UserCredentialResponseDto'
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lista as credenciais do próprio usuário corrente
      tags:
        - credentials
    post:
      description: >-
        Upsert por (usuário, provider). A chave é cifrada por envelope
        encryption antes de tocar o banco, e a resposta traz só a PROJEÇÃO —
        nunca o segredo, nem cifrado.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Registra ou substitui a API key de um provider
      tags:
        - credentials
  /users/me/credentials/{provider}:
    delete:
      description: 'Idempotente: apagar o que não existe também responde `{ 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: Sem token, token expirado ou assinatura inválida.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Remove a credencial de um provider
      tags:
        - credentials
  /users/me/credentials/{provider}/test:
    post:
      description: >-
        A checagem é uma ação explícita, separada do registro (ADR 0050): a api
        decifra o segredo, chama o provider e devolve SÓ o veredito — a chave
        nunca volta, nem em parte. Responde 200 para os três desfechos, porque o
        pedido foi processado; `refused` é um desfecho, não erro de protocolo.
        404 quando não há credencial para (usuário, provider) — aí não há o que
        testar.
      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: Sem token, token expirado ou assinatura inválida.
        '404':
          description: Sem credencial para este provider.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Confere contra o provider a credencial JÁ guardada
      tags:
        - credentials
  /users/me/git-credentials:
    post:
      description: >-
        Caminho próprio, não o de `users/me/credentials`, porque o corpo é
        diferente (`token`, e o enum de provider é `github`/`gitlab`). O token é
        cifrado e guardado sem verificação prévia (ADR 0050); para verificá-lo,
        `POST /users/me/credentials/{provider}/test`. Leitura, remoção e teste
        reusam as rotas de `users/me/credentials` — é a mesma tabela.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Registra um token de git (cifrado, sem testar)
      tags:
        - credentials
  /users/me/machine-device-keys:
    get:
      description: >-
        Por conta, sem projeto no caminho (RN-611): a instalação de uma linha
        cria uma chave de máquina antes de existir qualquer projeto, e toda
        outra listagem é por projeto. Devolve SÓ chaves de máquina (`especie:
        "maquina"`, `projectId: null`) e SÓ as do chamador — chaves de projeto
        ficam na listagem do projeto delas, e ninguém vê chave de outro usuário
        (RN-519). Chaves revogadas ENTRAM: registrar uma chave de máquina nova
        revoga a anterior (RN-552), e é nesta lista que isso aparece.
        `lastUsedAt` é uso registrado, nunca conexão viva; nulo quer dizer que a
        chave nunca foi usada.
      operationId: MachineDeviceKeysController_listMachineDeviceKeys
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/RunnerDeviceKeyListResponseDto'
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: >-
        Lista as próprias chaves de dispositivo de MÁQUINA do usuário
        autenticado
      tags:
        - users
  /users/me/machine-device-keys/{deviceKeyId}:
    delete:
      description: >-
        Idempotente — revogar de novo não é erro. Mesma revogação de `DELETE
        /projects/{projectId}/runner-device-keys/{deviceKeyId}`: também derruba
        as conexões VIVAS abertas com esta chave, em TODO projeto
        (RN-520/RN-543). Desde a RN-685 (ADR 0201) o alvo é a CHAVE: outro
        runner do mesmo usuário, conectado com PAT ou com outra chave, fica de
        pé. Os tickets da chave ainda não usados são anulados. Sem projeto
        ainda, não há conexão a derrubar. Chave de PROJETO, chave que não existe
        e chave de outro usuário respondem o mesmo 404.
      operationId: MachineDeviceKeysController_revokeMachineDeviceKey
      parameters:
        - name: deviceKeyId
          required: true
          in: path
          schema:
            type: string
      responses:
        '204':
          description: Chave revogada. Sem corpo.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '404':
          description: >-
            Nenhuma chave de máquina com este id pertence a quem chama (não
            existe, é chave de projeto, ou é de outra pessoa — uma resposta para
            todos).
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: >-
        Revoga uma das próprias chaves de dispositivo de MÁQUINA do usuário
        autenticado
      tags:
        - users
  /users/me/preferences:
    get:
      operationId: UserPreferencesController_get
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserPreferencesResponseDto'
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: >-
        Lê o idioma da interface e o idioma de resposta dos agentes do usuário
        autenticado
      tags:
        - users
    patch:
      description: >-
        Os dois campos são opcionais e independentes — mandar um nunca toca o
        outro, e corpo sem nenhum é 400. `locale` (a interface) continua fechado
        à lista `pt-BR`/`en`. `responseLanguage` (RN-618) é `automatico` ou
        qualquer código BCP-47 reconhecido, gravado na forma canônica; código
        não reconhecido é 400 e nada do corpo é gravado.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: >-
        Grava o idioma da interface e/ou o idioma de resposta dos agentes do
        usuário autenticado
      tags:
        - users
  /users/me/preferences/detected-language:
    post:
      description: >-
        O único jeito de um idioma detectado virar preferência (RN-624): a
        detecção sozinha nunca muda nada. `confirm` grava `detectedLanguage` +
        `detectedLanguageConfirmedAt`; `decline` registra a recusa para o mesmo
        idioma não ser perguntado de novo. Devolve as preferências depois da
        resposta.
      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: Código que não é um idioma BCP-47 reconhecido.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '409':
          description: >-
            `deteccao_mudou`: no `confirm`, as suas mensagens recentes não
            apontam mais para aquele idioma — nada foi gravado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Responde a pergunta do idioma detectado
      tags:
        - users
  /workspaces:
    get:
      description: >-
        Já filtrada por associação — não há listagem global. Cada item traz o
        papel do próprio chamador.
      operationId: WorkspacesController_list
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/WorkspaceComPapelResponseDto'
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no workspace.
        '404':
          description: O workspace não existe ou é invisível para quem chama.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lista os workspaces de quem chama
      tags:
        - workspaces
    post:
      description: >-
        Não exige papel: quem cria vira `owner`. É a única porta de entrada para
        quem ainda não pertence a workspace nenhum.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no workspace.
        '404':
          description: O workspace não existe ou é invisível para quem chama.
        '409':
          description: Já existe um workspace com este slug.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Cria um workspace
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no workspace.
        '404':
          description: O workspace não existe ou é invisível para quem chama.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Devolve um workspace pelo 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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no workspace.
        '404':
          description: O workspace não existe ou é invisível para quem chama.
        '409':
          description: Já existe um workspace com este slug.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Muda o nome ou o slug do workspace
      tags:
        - workspaces
    delete:
      description: Exige `owner`. Leva junto os projetos, as sessões e o histórico deles.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no workspace.
        '404':
          description: O workspace não existe ou é invisível para quem chama.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Remove o workspace
      tags:
        - workspaces
  /workspaces/{workspaceId}/containers:
    get:
      description: >-
        Uma linha por projeto do workspace — desde a RN-521 um projeto que nunca
        provisionou container está PRESENTE com `registrado: null` em vez de
        ausente, porque esta página é o caminho humano para subir o primeiro. O
        estado observado só é perguntado ao broker para linhas
        `provisioning`/`running`, e só até um orçamento por carga — ver
        `naoVerificado` nas linhas puladas, e o ADR 0136 para o raciocínio.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no workspace.
        '404':
          description: O workspace não existe ou é invisível para quem chama.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lista todo projeto do workspace e o container dele, se houver
      tags:
        - projects
  /workspaces/{workspaceId}/credential-spend:
    get:
      description: >-
        Existe porque a chave que os agentes gastam é a do DONO do workspace
        (RN-058) — quem paga a fatura precisa ver a fatura. Agrupa por provider
        porque essa é a unidade da credencial, e separa o que saiu por AGENTE do
        que saiu por uma pessoa no chat: os dois saem da mesma chave e respondem
        perguntas diferentes. Não devolve segredo nenhum.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Exige `owner` no workspace.
        '404':
          description: Projeto ou sessão não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Quanto as chaves do owner gastaram, por provider e por mês
      tags:
        - llm
  /workspaces/{workspaceId}/huggingface/models:
    get:
      description: >-
        Filtra por padrão só publishers OFICIAIS (o allowlist em
        `domain/llm/huggingface-official-publishers.ts`) — o mesmo espírito de
        "curadoria manual sempre" do catálogo de LLM (ADR 0042), aplicado ao
        selo de "oficial" em vez da ativação. `includeCommunity=true` traz todo
        publisher, cada um marcado `official: false` para a tela desenhar o
        aviso de segurança — nunca escondido.
      operationId: HuggingFaceModelsController_search
      parameters:
        - name: q
          required: true
          in: query
          description: Texto de busca.
          schema:
            type: string
        - name: includeCommunity
          required: false
          in: query
          description: Passe `true` para incluir também publishers não oficiais.
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/HuggingFaceModelResponseDto'
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Exige `maintainer` no workspace.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Busca modelos GGUF no Hugging Face Hub
      tags:
        - llm
  /workspaces/{workspaceId}/huggingface/pull-requests:
    post:
      description: >-
        Primeiro de dois passos explícitos: esta chamada só registra a intenção,
        em `pending_confirmation`. Nada é baixado até uma chamada separada a
        `POST .../pull-requests/:id/confirm` — o produto nunca roda um pull
        automático e silencioso.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Exige `maintainer` no workspace.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Cria um pedido de pull PENDENTE — nada é baixado ainda
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Exige `maintainer` no workspace.
        '404':
          description: Pedido de pull não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Status de um pedido de pull, para o frontend consultar
      tags:
        - llm
  /workspaces/{workspaceId}/huggingface/pull-requests/{id}/confirm:
    post:
      description: >-
        Move `pending_confirmation` → `confirmed` → `pulling`, chama o daemon do
        Ollama e, em caso de sucesso, faz upsert do modelo no catálogo
        (`models`) e o ativa para ESTE workspace. Em caso de falha o pedido
        termina em `failed` com um motivo que declara a origem (infra | modelo |
        código | política) — nunca falha calada. Esta chamada roda de forma
        SÍNCRONA durante o pull inteiro (ver o caso de uso para o porquê); use a
        rota GET abaixo para consultar em vez de segurar esta conexão aberta.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Exige `maintainer` no workspace.
        '404':
          description: Pedido de pull não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: A segunda confirmação — roda o pull de fato
      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: Sem token, token expirado ou assinatura inválida.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lists the LLM providers the workspace owner has a key for
      tags:
        - llm
  /workspaces/{workspaceId}/members:
    get:
      description: >-
        Quem está associado ao WORKSPACE, com o papel de workspace — o papel que
        todo projeto herda a menos que uma linha de projeto o sobreponha.
        Completa `GET projects/:projectId/members`, que lista só linhas de
        projeto: quem alcança um projeto só pelo workspace aparece aqui e não
        lá. Só id, nome, e-mail e papel.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no workspace.
        '404':
          description: O workspace não existe ou é invisível para quem chama.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lista os membros do workspace
      tags:
        - workspaces
    post:
      description: >-
        Só `owner` mexe na lista de membros. O papel aqui é herdado por TODOS os
        projetos do workspace. Esse papel é NECESSÁRIO mas não SUFICIENTE: mudar
        o SEU PRÓPRIO papel aqui é recusado com 403 nos dois sentidos, e não
        pode ser habilitado em lugar nenhum. Não há nível acima para segurar a
        queda, então um autorrebaixamento seria irrecuperável pela interface.
        Rebaixar OUTRO `owner` continua permitido — é o único jeito de revogar
        propriedade.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: >-
            Não é `owner` do workspace, OU o alvo é quem chama e o papel pedido
            difere do atual dele (teto de automovimento).
        '404':
          description: O workspace não existe ou é invisível para quem chama.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Associa um usuário ao workspace
      tags:
        - workspaces
  /workspaces/{workspaceId}/members/{userId}:
    delete:
      description: >-
        Só `owner`, como o upsert. Remove a linha do workspace E, na mesma
        transação, as linhas do usuário em todo projeto deste workspace (o papel
        de projeto sobrepõe o de workspace, então deixá-las manteria o usuário
        dentro desses projetos), mais as chaves de dispositivo de PROJETO e os
        personal access tokens do usuário neles. Depois a conexão de runner viva
        desse usuário em cada projeto é derrubada, em melhor esforço. Chaves de
        dispositivo de máquina e sessões abertas ficam como estão — deixam de
        alcançar este workspace porque o papel resolve para nenhum.


        Remover a SI MESMO é recusado com 403 e não pode ser habilitado em lugar
        nenhum: não há nível acima para segurar a queda. Essa mesma cláusula é o
        que protege o último `owner` — só um `owner` chama esta rota e ninguém
        se remove, então toda chamada bem-sucedida deixa ao menos quem chamou
        como `owner`. Remover OUTRO `owner` é permitido — é como se revoga
        propriedade —, exceto o TITULAR (`createdBy`), recusado com 409
        `criador_do_workspace` até `PUT :workspaceId/owner-of-record` passá-lo a
        outro owner: a chave de LLM que os agentes gastam e o relatório de gasto
        são dele. Idempotente: alvo sem linha de workspace ainda tem as linhas
        de projeto deste workspace limpas, e recebe `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: Associação removida. Sem corpo.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: >-
            Não é `owner` do workspace, OU o alvo é quem chama (teto de
            autorremoção).
        '404':
          description: O workspace não existe ou é invisível para quem chama.
        '409':
          description: >-
            `criador_do_workspace`: o alvo é o titular; transfira a titularidade
            antes.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Desassocia um usuário do workspace
      tags:
        - workspaces
  /workspaces/{workspaceId}/model-binding:
    get:
      description: O binding CRU deste escopo. É a raiz da cascata.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no escopo.
        '404':
          description: Escopo não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lê o binding de modelo do workspace
      tags:
        - llm
    put:
      description: Vale para todo projeto que não tem binding próprio.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no escopo.
        '404':
          description: Escopo não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Define o modelo padrão do workspace
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Exige `owner` no workspace.
        '404':
          description: Modelo não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Sets a model's curated billing mark (api | subscription | free)
      tags:
        - llm
  /workspaces/{workspaceId}/models/{modelId}/price-changes:
    get:
      description: >-
        A mais recente primeiro. É um log imutável: nunca recebe UPDATE, e a
        linha traz o par antes/depois para que a auditoria não dependa de
        reconstruir o "antes" a partir da linha anterior.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no workspace.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Histórico das mudanças de preço de um modelo
      tags:
        - llm
  /workspaces/{workspaceId}/models/{modelId}/pricing:
    patch:
      description: >-
        Vale daqui em diante e NUNCA reprecifica o passado: cada linha de
        `token_usage` guarda o preço que produziu o custo dela. A mudança fica
        registrada com o par antes/depois — consultável na rota de histórico.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Exige `owner` no workspace.
        '404':
          description: Modelo não encontrado.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Corrige o preço de um modelo
      tags:
        - llm
  /workspaces/{workspaceId}/models/activate:
    post:
      description: >-
        Curadoria do owner, VALENDO SÓ PARA ESTE WORKSPACE (ADR 0049). Só toca
        `isActive` — `availability` é o que o sync observou no provider, é
        global, e não muda aqui. Lote inteiro ou nada: id inexistente reprova a
        chamada sem aplicar linha nenhuma.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Exige `owner` no workspace.
        '404':
          description: Algum id do lote não existe.
        '422':
          description: >-
            Ativar um alias de roteamento livre do OpenRouter (`~…`) — `code:
            "alias_de_roteamento_livre"`, com os `modelIds` recusados. O lote
            inteiro é recusado. Desativar um é sempre permitido (AT-271,
            RN-679).
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Liga ou desliga modelos no seletor (em lote)
      tags:
        - llm
  /workspaces/{workspaceId}/models/catalog:
    get:
      description: >-
        A tela de curadoria. Modelo descoberto pelo sync entra como `isActive:
        false` e só aparece aqui até alguém ativá-lo; modelo que sumiu do
        provider vem com `availability: "unavailable"` e nunca é apagado, porque
        bindings e histórico de custo apontam para ele. `isActive` é PARA ESTE
        workspace (ADR 0049); `availability` é global, porque é o que o sync
        observou no provider.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no workspace.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lista o catálogo INTEIRO, inclusive inativos e indisponíveis
      tags:
        - llm
  /workspaces/{workspaceId}/models/sync:
    post:
      description: >-
        Mesmo caso de uso da rota interna que o engine agenda — o botão de
        atualizar da tela de curadoria não tem caminho próprio, então nunca há
        duas reconciliações que possam divergir.
      operationId: ModelsController_sync
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SyncModelCatalogResponseDto'
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Exige `owner` no workspace.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Força agora o sync do catálogo que o job periódico faz
      tags:
        - llm
  /workspaces/{workspaceId}/models/uses:
    post:
      description: >-
        O eixo que nenhum catálogo publica: "bom para código" não é capability
        declarada por provider nenhum, é opinião do operador — e vale SÓ para
        este workspace (ADR 0049). Eixo independente da ativação: marcar um uso
        não liga o modelo no seletor, e mudar o uso não desliga o que estava
        ligado. A lista de usos SUBSTITUI a anterior.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Exige `owner` no workspace.
        '404':
          description: Algum id do lote não existe.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Marca para que o workspace usa cada modelo (em lote)
      tags:
        - llm
  /workspaces/{workspaceId}/owner-of-record:
    put:
      description: >-
        O titular é `createdBy`: de quem é a credencial de LLM que os agentes
        gastam e a credencial de git que as ações de agente usam (RN-058), e de
        quem é o relatório de gasto (RN-060). Não concede autorização — quem
        concede são os papéis. O alvo já tem de ser `owner` do workspace (senão
        409 `titular_precisa_ser_owner`). A partir do commit, os turnos de
        agente buscam a credencial do NOVO titular; se ele não tem uma para o
        provider do modelo, o turno termina com o desfecho de sempre de "nenhuma
        credencial cadastrada" para aquele provider — esta rota não confere isso
        antes. Qualquer `owner` pode chamá-la, inclusive para outro owner.
        Transferir para o titular atual não faz nada.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no workspace.
        '404':
          description: O workspace não existe ou é invisível para quem chama.
        '409':
          description: '`titular_precisa_ser_owner`: o alvo não é `owner` do workspace.'
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Transfere a titularidade do workspace
      tags:
        - workspaces
  /workspaces/{workspaceId}/project-folders:
    get:
      description: >-
        O navegador de pastas do modo Mounted, servido pela api em vez do runner
        (RN-504). Escopado RIGIDAMENTE a `BRABO_PROJECTS_BASE` (ADR 0141):
        `path` é opcional e tem como padrão a própria base, e qualquer `path`
        fora dela é `400` — pedido malformado, não problema de permissão, já que
        papel nenhum navega fora da base.


        Com teto de propósito: só diretórios, no máximo 500 (ordenados ANTES do
        corte, com `truncado` dizendo isso), nunca recursivo, entradas com ponto
        no início excluídas, e symlinks relatados mas nunca seguidos. O que fica
        de fora é CONTADO (`arquivos`, `simbolicos`) para uma pasta cheia de
        código nunca parecer vazia.


        Não há POST irmão: criar pasta é parte de materializar o workspace
        montado, nunca do navegador.
      operationId: WorkspacesController_listProjectFolders
      parameters:
        - name: path
          required: false
          in: query
          description: >-
            Caminho absoluto a listar. Omitido quer dizer a própria base. Tem de
            estar dentro de `BRABO_PROJECTS_BASE`; `..`, `.` e caminhos
            relativos são recusados de cara em vez de resolvidos.
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectFoldersResponseDto'
        '400':
          description: O caminho está fora da base de projetos, ou malformado.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no workspace.
        '404':
          description: O workspace não existe ou é invisível para quem chama.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lista as subpastas de uma pasta dentro da base de projetos
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no workspace.
        '404':
          description: O workspace não existe ou é invisível para quem chama.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Lista os projetos do workspace
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no workspace.
        '404':
          description: O workspace não existe ou é invisível para quem chama.
        '409':
          description: Já existe um projeto com este slug no workspace.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Cria um projeto dentro do workspace
      tags:
        - workspaces
  /workspaces/{workspaceId}/projects-base:
    get:
      description: >-
        A única pasta da máquina do operador que os containers da api e do
        engine enxergam, montada por identidade (ADR 0141). `null` — estado
        normal, nunca erro — quer dizer que esta instalação não tem
        `BRABO_PROJECTS_BASE`, então o assistente de projeto não deve oferecer o
        modo Mounted de jeito nenhum. O mesmo valor para todo workspace: é
        configuração da instalação, e `workspaceId` só escopa a autorização.


        `brokerConfigurado` responde a outra metade da mesma pergunta:
        `container` e `mounted` só sobem container pelo broker (ADR 0144), então
        sem ele o assistente não pré-seleciona Mounted e oferece Runner no lugar
        (ADR 0161, RN-573).
      operationId: WorkspacesController_getProjectsBase
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectsBaseResponseDto'
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no workspace.
        '404':
          description: O workspace não existe ou é invisível para quem chama.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: A pasta base dos projetos no modo Mounted
      tags:
        - workspaces
  /workspaces/{workspaceId}/projects-status:
    get:
      description: >-
        Uma linha por projeto do workspace com tarefa bloqueada — alimenta o
        ponto de status da barra lateral do painel com uma consulta só em vez de
        N chamadas ao 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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no workspace.
        '404':
          description: O workspace não existe ou é invisível para quem chama.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Contagem de tarefas bloqueadas por projeto
      tags:
        - workspaces
  /workspaces/{workspaceId}/projects-summary:
    get:
      description: >-
        Uma linha por projeto do workspace com provider de git, status de
        provisionamento, orçamento, última atividade, histórias aguardando
        promoção e os fatos que decidem a lista de agentes (RN-090).


        Mesma ideia de `projects-status`, um andar acima: a grade de cards é uma
        leitura de WORKSPACE, não N leituras de projeto. O caminho anterior
        fazia sete consultas com poll por card, e com 23 projetos só o painel
        estourava o rate limit de 300 req/min.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no workspace.
        '404':
          description: O workspace não existe ou é invisível para quem chama.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Tudo o que os cards do painel desenham, numa chamada só
      tags:
        - workspaces
  /workspaces/{workspaceId}/spend-report:
    get:
      description: >-
        O público do OWNER. O eixo de PROVIDER voltou (ADR 0076, RN-186) e está
        aqui, não na rota do membro, porque quebrar por provider é quebrar por
        CREDENCIAL — e credencial é assunto de quem paga (RN-060). Complementa
        `credential-spend`, que continua respondendo a pergunta da FATURA (por
        mês, com o vínculo à chave que existe hoje). Pessoa e agente também vêm
        em blocos separados, por `actor_kind`. Janela deslizante em dias, e a
        série diária vem DENSA — dia sem gasto entra como zero, senão o
        sparkline mente sobre o ritmo.
      operationId: SpendController_getWorkspaceSpendReport
      parameters:
        - name: workspaceId
          required: true
          in: path
          schema:
            type: string
        - name: dias
          required: false
          in: query
          description: Janela deslizante. Padrão 30, máximo 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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Exige `owner` no workspace.
        '404':
          description: Project not in this workspace.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Quebra o gasto do workspace por modelo, provider, projeto, ator e dia
      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: Janela deslizante. Padrão 30, máximo 180.
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MyWorkspaceSpendResponseDto'
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: My own consumption in this workspace, summed by project
      tags:
        - llm
  /workspaces/{workspaceId}/summary:
    get:
      description: >-
        Contagem de projetos, agentes que gastaram tokens neste mês e o gasto do
        mês — alimenta a linha de resumo do painel de projetos.
      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: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no workspace.
        '404':
          description: O workspace não existe ou é invisível para quem chama.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Resumo agregado do workspace
      tags:
        - workspaces
  /workspaces/{workspaceId}/tool-router:
    put:
      description: >-
        Ligado por padrão. Quando ligado E o modelo do turno é do OpenRouter, um
        passo de agente com duas ou mais ferramentas primeiro pergunta ao Jev
        qual ferramenta cabe, e ao modelo de chat só são oferecidas essa
        ferramenta e a anterior. O Jev nunca aprova nem nega nada: ação que
        exige aprovação continua exigindo. Qualquer falha do Jev cai no catálogo
        inteiro. Desligá-lo para toda chamada ao Jev, e o custo delas junto.
      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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no workspace.
        '404':
          description: O workspace não existe ou é invisível para quem chama.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Liga ou desliga o roteamento de ferramentas do Jev no workspace
      tags:
        - workspaces
  /workspaces/{workspaceId}/unread-events:
    post:
      description: >-
        A gaveta do sino, para o workspace inteiro (RN-091). Cada cursor diz até
        qual `seq` aquele projeto já foi lido, e a resposta traz o que veio
        depois disso na sessão mais recente de cada um.


        **É POST porque é o único verbo com CORPO, não porque muda estado.** O
        servidor não sabe onde cada leitor parou: "lido até aqui" é um `seq` por
        projeto guardado no navegador, e (de propósito) não há endpoint de
        marcar como lido. Então o corte precisa VIAJAR no pedido, e são dezenas
        de pares — numa query string isso vira uma URL longa, que um proxy
        corta, e ainda poria os ids de projeto do usuário em logs de acesso. A
        rota é idempotente e não tem efeito colateral: responde `200`, nunca
        `201`.


        Substitui um pedido POR PROJETO com a gaveta aberta — 23 projetos
        custavam 286 req/min contra um limite de 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: >-
            Corpo inválido. O `ValidationPipe` roda com `whitelist` e
            `forbidNonWhitelisted`, então campo desconhecido também reprova.
        '401':
          description: Sem token, token expirado ou assinatura inválida.
        '403':
          description: Papel insuficiente no workspace.
        '404':
          description: O workspace não existe ou é invisível para quem chama.
        '429':
          description: Rate limit por usuário ou por IP.
      security:
        - bearer: []
      summary: Eventos não lidos de vários projetos, numa chamada só
      tags:
        - workspaces
info:
  title: Brabo API
  description: >-
    A API do Brabo. Todo endpoint devolve JSON, exceto os quatro explicitamente

    documentados com outro `content-type` (os dois de SSE, o de métricas,

    e o callback OAuth, que redireciona).


    ## Autenticação


    O access token é um **JWT EdDSA de 15 minutos**, apresentado em

    `Authorization: Bearer <token>`. A chave pública mora em

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


    O refresh **não** é JWT: é um token opaco que mora num cookie

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

    nunca aparece em corpo de resposta nenhum. `POST /auth/refresh` e

    `POST /auth/logout` leem o cookie e exigem que o cabeçalho `X-CSRF-Token`

    bata com o cookie `brabo_csrf`.


    **A rotação é obrigatória.** Cada refresh consome o token apresentado e

    emite outro. Reapresentar um já consumido é assinatura de roubo

    e revoga a família INTEIRA — inclusive a sessão legítima. Um cliente

    que dispara dois refreshes concorrentes derruba a si mesmo; use uma única

    chamada em voo.


    As rotas `/internal/*` são a superfície api ↔ engine e ficam **fora do

    JWT**: exigem o cabeçalho `X-Brabo-Service-Token` com o segredo

    compartilhado. Token de usuário não abre nenhuma delas.


    ## Erros


    Formato padrão do NestJS: `{ "statusCode": number, "message": string |
    string[], "error": string }`.


    | código | quando |

    |---|---|

    | `400` | corpo inválido — o `ValidationPipe` roda com `whitelist` e
    `forbidNonWhitelisted`, então campo desconhecido também reprova |

    | `401` | sem token, token expirado ou credencial inválida |

    | `403` | autenticado mas sem o papel que o RBAC exige, ou service token
    errado |

    | `404` | o recurso não existe **ou** é invisível para quem chama |

    | `409` | conflito de estado — transição de sessão inválida, recurso já
    existe |

    | `429` | rate limit ou bloqueio progressivo |


    As rotas de auth respondem de forma **deliberadamente indistinguível** entre

    e-mail inexistente, senha errada e conta bloqueada: mesmo corpo, mesmo
    status, mesmo

    tempo. É isso que fecha a enumeração de contas, e não é defeito de

    diagnóstico.


    ## Rate limit


    Janela deslizante no Postgres: 300 requisições por usuário e 600 por IP a
    cada 60s,

    configurável. Rota `@Public()` e rota de serviço são **isentas** — o que

    protege `/auth/*` é o bloqueio progressivo, não o rate limit.
  version: 0.1.0
  contact: {}
tags:
  - name: auth
    x-displayName: autenticação
  - name: gates
    x-displayName: gates
  - name: git
    x-displayName: git
  - name: infrastructure
    x-displayName: infraestrutura
  - name: internal
    x-displayName: interno
  - name: llm
    x-displayName: llm
  - name: projects
    x-displayName: projetos
  - name: actions
    x-displayName: ações
  - name: execution
    x-displayName: execução
  - name: anamnesis
    x-displayName: anamnese
  - name: backlog
    x-displayName: backlog
  - name: psychologist
    x-displayName: psicólogo
  - name: rag
    x-displayName: rag
  - name: sessions
    x-displayName: sessões
  - name: agents
    x-displayName: agentes
  - name: credentials
    x-displayName: credenciais
  - name: users
    x-displayName: usuários
  - name: workspaces
    x-displayName: workspaces
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: >-
            Nome do módulo, como aparece no `module_map`. Sobe um dev agent
            dedicado a ele, num worktree isolado.
      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: >-
            Por que uma pessoa ainda precisa clicar. `falhou`: o sistema tentou
            e o aceite falhou — `handoff.auto_accept_failed` está no event log.
      required:
        - aceito
    AceiteResponseDto:
      type: object
      properties:
        message:
          type: string
          example: If the address is available, we've sent an email.
          description: >-
            Mensagem genérica, idêntica para endereço conhecido e desconhecido —
            ver a nota de enumeração na referência.
      required:
        - message
    ActivateExecutionDto:
      type: object
      properties:
        taskBudgetMicros:
          type: number
          example: 500000
          description: >-
            Orçamento por tarefa em micro-USD. Omitido usa o padrão do domínio
            (DEFAULT_TASK_BUDGET_MICROS).
        maxGateCorrections:
          type: number
          example: 3
          description: >-
            Quantas idas e voltas dev↔gate antes de a tarefa virar `blocked`. É
            o que impede um agente de gastar o orçamento num laço de correção.
        devAgentImpl:
          type: string
          enum:
            - real
            - noop
          example: real
          description: >-
            `noop` sobe os dev agents SEM LLM, para exercitar a infraestrutura
            de execução de ponta a ponta sem gastar tokens. Omitido usa `real`.
        terminalAllowPatterns:
          example:
            - Terminal(pnpm test:*)
            - Terminal(pnpm lint)
          description: >-
            Padrões liberados no `permissions.json` para os dev agents rodarem a
            suíte de testes. `deny` continua vencendo qualquer coisa listada
            aqui.
          type: array
          items:
            type: string
        originSessionId:
          type: string
          example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
          description: >-
            Id da sessão de CHAT de onde veio o clique "ativar execução" — a
            sessão criativa/consultiva do Dev Lead/PO, nunca a de execução.
            Omitido (chamador legado, ex.: ativação pela Visão geral sem
            contexto de sessão) não fecha sessão nenhuma, como antes. Quando
            dado, ela é fechada no fim — mas só se não tiver handoff/ação/turno
            pendente (RN-135), e nunca se for a própria sessão de execução.
    ActorDto:
      type: object
      properties:
        kind:
          type: string
          enum:
            - user
            - agent
            - system
          example: agent
          description: Natureza de quem agiu.
        id:
          type: string
          example: dev-api
          description: >-
            O id do usuário quando `kind=user`; o slug do agente quando
            `kind=agent`.
      required:
        - kind
        - id
    ActorResponseDto:
      type: object
      properties:
        kind:
          type: string
          enum:
            - user
            - agent
            - system
          example: agent
          description: Natureza de quem agiu.
        id:
          type: string
          example: dev-api
          description: >-
            O id do usuário quando `kind=user`; o slug do agente quando
            `kind=agent`.
      required:
        - kind
        - id
    AddMemberDto:
      type: object
      properties:
        userId:
          type: string
          format: uuid
          example: 3f1b2c8e-5a4d-4b7e-9c10-2d6f8a1b4c33
          description: Id do usuário a associar.
        role:
          type: string
          enum:
            - viewer
            - developer
            - maintainer
            - owner
          example: developer
          description: >-
            Papel nesta associação. Num PROJETO ele SOBREPÕE o papel de
            workspace nos dois sentidos — o papel efetivo é este sempre que a
            associação existe, maior OU menor — e dois movimentos são recusados
            com 403: rebaixar um `owner` do workspace, e mudar o próprio papel
            (em qualquer sentido). Num WORKSPACE é simplesmente o papel, só com
            o segundo desses tetos: você não muda o seu.
      required:
        - userId
        - role
    AdoptRepositoryDto:
      type: object
      properties:
        externalId:
          type: string
          example: acme/checkout
          description: >-
            O identificador do repositório NO PROVIDER: `owner/repo` no github,
            `namespace/path` no gitlab, o caminho absoluto do repositório bare
            no local. Nada é criado — o repositório já tem de existir e a
            credencial registrada tem de alcançá-lo.
      required:
        - externalId
    AdoptRepositoryResponseDto:
      type: object
      properties:
        repository:
          $ref: '#/components/schemas/ProvisionedRepositoryResponseDto'
        plan:
          description: >-
            O DRY-RUN: nada foi executado no repositório. Decida por `POST
            .../bootstrap/plan/approve` ou `.../plan/skip`.
          allOf:
            - $ref: '#/components/schemas/BootstrapPlanResponseDto'
        alreadyAdopted:
          type: boolean
          example: false
          description: >-
            `true` quando o projeto já tinha adotado ESTE repositório — nada foi
            criado, o plano só foi regenerado sobre o estado atual.
      required:
        - repository
        - plan
        - alreadyAdopted
    AdrRefResponseDto:
      type: object
      properties:
        actionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRC
          description: Id da ação `open_adr_pr` (uma `proposed_action`) que abriu o 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` enquanto a PR não foi de fato aberta.'
      required:
        - actionId
        - title
        - status
        - pullRequestUrl
    AgentAutonomyRuleResponseDto:
      type: object
      properties:
        agentId:
          type: string
          example: dev-api
          description: Slug do agente.
        actionType:
          type: string
          example: terminal
          description: >-
            O tipo de ação, ou `"*"` — o "modo automático" (RN-153): autonomia
            para QUALQUER tipo de ação deste agente.
        mode:
          type: string
          enum:
            - auto_approve
            - require_approval
            - deny
          example: auto_approve
          description: >-
            Autonomia concedida. Não sobrepõe o `permissions.json`: um `deny`
            ali continua vencendo.
      required:
        - agentId
        - actionType
        - mode
    AgenteAtivadoResponseDto:
      type: object
      properties:
        agent:
          type: string
          example: po
          description: Slug do agente ativado.
        status:
          type: string
          example: active
          enum:
            - active
      required:
        - agent
        - status
    AgenteComVersoesResponseDto:
      type: object
      properties:
        agent:
          type: string
          example: dev-api
          description: >-
            Slug real, inclusive os dev agents por módulo. A listagem NÃO parte
            de uma lista estática — se partisse, os agentes que de fato existem
            não apareceriam.
        versions:
          type: array
          items:
            $ref: '#/components/schemas/InstructionVersionResponseDto'
      required:
        - agent
        - versions
    AgentTokenUsageResponseDto:
      type: object
      properties:
        actorId:
          type: string
          example: dev-api
          description: O slug do agente, ou o id do usuário.
        costMicros:
          type: number
          example: 3400000
          description: Custo em micro-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: >-
            A ORIGEM da falha, nunca deduzida por eliminação (lição do 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: >-
            Catálogo FECHADO. O modelo não pode emitir competência fora dele — a
            validação recusa —, então ele entra no prompt como lista.
          type: array
          items:
            type: string
        members:
          description: Já EXCLUI quem optou por não ser perfilado.
          type: array
          items:
            $ref: '#/components/schemas/AnamneseMemberResponseDto'
        queuedHypotheses:
          description: As hipóteses ACEITAS do Psicólogo esperando virar insumo priorizado.
          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: >-
            As aprovações e recusas do usuário dentro da janela. Vêm por aqui
            porque NÃO estão no event log.
          type: array
          items:
            $ref: '#/components/schemas/AnamneseDecisionResponseDto'
        windowFrom:
          type: object
          example: '2026-07-27T12:00:00.000Z'
          nullable: true
          description: >-
            Início da janela a analisar (fim da última rodada). `null` na
            primeira. O engine lê os eventos em si direto do Postgres —
            carregá-los por HTTP seria mais caro sem ser mais correto.
      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: >-
            O idioma sobre o qual era a pergunta — o `detectionQuestion` de `GET
            .../sessions/:sessionId/response-language`.
        answer:
          type: string
          enum:
            - confirm
            - decline
          example: confirm
          description: >-
            `confirm` o grava como o seu idioma detectado-e-confirmado (ele
            passa a responder a você onde a sua Conta estiver em automático);
            409 `deteccao_mudou` quando as suas mensagens não apontam mais para
            ele. `decline` grava a recusa e este idioma nunca mais é perguntado
            (confirmá-lo depois limpa a recusa).
      required:
        - language
        - answer
    AnswerOpenQuestionDto:
      type: object
      properties:
        resposta:
          type: string
          example: 404 for a code that does not exist.
          description: 'Resposta em texto livre. Mande isto OU `aceitarSugestao: true`.'
        aceitarSugestao:
          type: boolean
          example: true
          description: Aceita a sugestão do agente como resposta.
    AnswerStructuredQuestionDto:
      type: object
      properties:
        answers:
          type: object
          additionalProperties:
            type: string
          example:
            name: Easy Checkout
            users: Small business owners
            platform: Web
          description: >-
            Resposta indexada pelo `id` da pergunta — o mesmo `id` que veio em
            `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: >-
            Tipo do evento. O inventário completo está em
            `docs/reference/events.md`.
        actor:
          $ref: '#/components/schemas/ActorDto'
        payload:
          type: object
          additionalProperties: true
          example:
            role: user
            content: I want a new checkout.
          description: Forma livre, específica de cada `type`.
      required:
        - type
        - actor
        - payload
    AppendSessionEventInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        type:
          type: string
          example: agent.tool_call
          description: Ver `docs/reference/events.md`.
        actorKind:
          type: string
          enum:
            - user
            - agent
            - system
          example: agent
        actorId:
          type: string
          example: dev-api
          description: Slug do agente que o emitiu.
        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 da ação.
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        sessionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
        seq:
          type: number
          example: 7
          description: Ordem da ação dentro da sessão; usada como cursor nas listagens.
        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: O que a ação faria. `git_push` e `git_merge` exigem `maintainer`.
        payload:
          type: object
          additionalProperties: true
          example:
            command: pnpm test
          description: Parâmetros da ação, específicos do `actionType`.
        status:
          type: string
          enum:
            - pending
            - approved
            - denied
            - auto_approved
            - executed
            - failed
          example: pending
          description: >-
            Estado no pipeline. `auto_approved` é distinto de `approved` de
            propósito: o log precisa distinguir o que uma pessoa decidiu do que
            a política liberou.
        resolvedPolicy:
          type: string
          enum:
            - auto_approve
            - require_approval
            - deny
          example: require_approval
          description: >-
            O que o `permissions.json` decidiu para esta ação. `deny` SEMPRE
            vence `allow` — nem a autonomia do agente o sobrepõe.
        actor:
          description: Quem propôs.
          allOf:
            - $ref: '#/components/schemas/ActorResponseDto'
        decidedBy:
          type: object
          example: null
          nullable: true
          description: Id do usuário que aprovou ou recusou. Nunca um agente.
        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: >-
            Resultado da execução, com forma específica de cada `actionType` —
            saída e código de saída para terminal, número e URL da PR para os
            tipos de git. `null` enquanto a ação não foi executada.
        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`: este clique aprovou a ação. `ja_aprovada`: a ação já
            tinha saído de `pending` por uma aprovação (um clique anterior,
            outra pessoa ou a política) — sucesso idempotente, não erro. Ação
            RECUSADA continua sendo 409 `acao_ja_recusada`.
        padraoGravado:
          type: boolean
          example: true
          description: >-
            Se este clique gravou o padrão (e o evento `permission.granted`
            dele). `false` quer dizer que já existia; nada foi gravado.
      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`: a história não aponta para módulo nenhum.
            `missing_module`: aponta para um que não existe no mapa vigente.
        missing:
          example:
            - pagamentos
          description: Os módulos citados que não existem. Vazio quando `no_module`.
          type: array
          items:
            type: string
      required:
        - storyId
        - title
        - status
        - reason
        - missing
    ArchitectureResponseDto:
      type: object
      properties:
        moduleMap:
          nullable: true
          description: '`null` enquanto o Arquiteto não escreveu o primeiro mapa.'
          type: object
          allOf:
            - $ref: '#/components/schemas/ModuleMapResponseDto'
        adrs:
          type: array
          items:
            $ref: '#/components/schemas/AdrRefResponseDto'
        pendencies:
          description: Validação cruzada história↔mapa. Lista vazia é o estado saudável.
          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: Opcional; gravado em `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: Identificador da área. Único dentro do projeto.
        leadAgentId:
          type: string
          example: dev-lead
          description: O lead da área — o único contato externo dela.
        maxParallel:
          type: number
          example: 2
          description: >-
            Quantos agentes o lead pode ter na SESSÃO sem pedir autorização.
            Acima disso, uma `proposed_action`. O teto é por sessão, não por
            módulo: contar por módulo permitiria N módulos × N agentes sem
            autorização nenhuma.
        budgetMicros:
          type: object
          example: 20000000
          nullable: true
          description: >-
            O teto de gasto da área, em micro-USD (ADR 0110, RN-443). `null`
            quer dizer sem teto — uma checagem independente e ADITIVA ao lado
            dos orçamentos de projeto e de sessão, não uma cascata: o primeiro
            dos três que bater no teto bloqueia a chamada.
        spentMicros:
          type: number
          example: 4300000
          description: >-
            O gasto acumulado da área, em micro-USD. Cresce sempre que um agente
            desta área gasta, COM OU SEM teto definido.
        members:
          example:
            - dev-api
            - dev-web
          description: >-
            Os membros. Na área de dev eles vêm do `module_map`, um por módulo —
            é isso que impede a área de ser uma lista fixa no código.
          type: array
          items:
            type: string
      required:
        - id
        - projectId
        - key
        - leadAgentId
        - maxParallel
        - budgetMicros
        - spentMicros
        - members
    ArquivosTocadosResponseDto:
      type: object
      properties:
        itens:
          description: A mais recente primeiro, teto de 100.
          type: array
          items:
            $ref: '#/components/schemas/ArquivoTocadoResponseDto'
        total:
          type: number
          example: 140
          description: Total real, mesmo quando `itens` foi cortado.
        prsNaoLidas:
          type: number
          example: 0
          description: >-
            PRs das sessões lidas cujos arquivos NÃO foram lidos: além do teto
            de 20 PRs, ou o provider recusou o 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: >-
            PRs (`pr_open`/`open_adr_pr`) mais épicos, histórias e tarefas que
            este agente criou na sessão.
      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: >-
            Nomes do `module_map` vigente. Módulo que não existe vira pendência
            de arquitetura.
          type: array
          items:
            type: string
      required:
        - projectId
        - storyId
        - moduleIds
    AttachLocalFolderRequestDto:
      type: object
      properties:
        folderName:
          type: string
          example: brabo
          description: >-
            O nome da pasta como escolhido no navegador — mostrado no painel de
            cobertura.
        files:
          description: >-
            Até 500 arquivos. O teto exato de bytes somados
            (RAG_LOCAL_TOTAL_BYTES_LIMIT) e o allowlist de extensões por arquivo
            são aplicados pelo caso de uso, não por este DTO — um array de
            strings individualmente válidas ainda pode somar um payload grande
            demais.
          type: array
          items:
            $ref: '#/components/schemas/LocalFolderFileDto'
      required:
        - folderName
        - files
    AttachLocalFolderResponseDto:
      type: object
      properties:
        folderName:
          type: string
        filesIndexed:
          type: number
        filesSkipped:
          type: number
          description: >-
            Pulado por passar do teto de bytes por arquivo ou ter extensão não
            reconhecida — nunca reprova o lote inteiro.
        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: O slug do agente ou a chave da área, exatamente como pedido.
        binding:
          nullable: true
          description: >-
            O MESMO valor que a rota de chave única responde para esta chave —
            `null` quando nenhum nível da cascata tem modelo.
          type: object
          allOf:
            - $ref: '#/components/schemas/ResolvedBindingResponseDto'
      required:
        - key
        - binding
    BindingsResolvidosEmLoteResponseDto:
      type: object
      properties:
        agents:
          description: Uma entrada por agente pedido, na ordem pedida.
          type: array
          items:
            $ref: '#/components/schemas/BindingResolvidoDaChaveResponseDto'
        areas:
          description: Uma entrada por área pedida, na ordem pedida.
          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: >-
            Diagnóstico em texto livre — um detalhe humano do que aconteceu, não
            a origem estruturada da falha.
        origin:
          type: string
          enum:
            - infra
            - modelo
            - codigo
            - politica
          example: infra
          description: >-
            A ORIGEM da falha: `infra`, `modelo`, `codigo` ou `politica`. Nunca
            por eliminação — é a lição do ADR 0020, levada um nível acima pelo
            QA Lead no ADR 0038. Opcional: nem todo ponto de chamada sabe a
            origem.
      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` é INFORMATIVO e nunca bloqueia: um repositório
            adotado tem a política que tem, e o bootstrap não a apaga.
        detail:
          type: object
          example:
            branchName: develop
            protected: false
          additionalProperties: true
      required:
        - kind
        - detail
    BootstrapPlanEstadoResponseDto:
      type: object
      properties:
        plan:
          nullable: true
          description: '`null` quando o projeto não tem repositório adotado.'
          type: object
          allOf:
            - $ref: '#/components/schemas/BootstrapPlanResponseDto'
        decision:
          type: string
          enum:
            - approved
            - as_is
          example: null
          nullable: true
          description: >-
            `null` = plano gerado e AINDA não decidido. É o estado em que nada
            roda (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: >-
            O que o bootstrap FARIA. Lista vazia = o repositório já está como o
            template quer, e não há o que aprovar.
          type: array
          items:
            $ref: '#/components/schemas/BootstrapPlanStepResponseDto'
        diagnostics:
          description: As divergências entre o repositório e o template.
          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: >-
            A mesma taxonomia de `proposed_actions` — cada passo aprovado vira
            uma ação registrada quando o bootstrap roda.
        payload:
          type: object
          example:
            branchName: qa
            fromRef: dev
          description: 'Os dados da mutação: qual branch, qual arquivo, de onde.'
          additionalProperties: true
      required:
        - step
        - actionType
        - payload
    BudgetResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000ORCAMENTO000000001
        projectId:
          type: object
          example: 01JC4Z0000PROJETO0000000001
          nullable: true
          description: Preenchido para orçamento de projeto; `null` para o de sessão.
        sessionId:
          type: object
          example: null
          nullable: true
          description: O contrário do de cima.
        limitMicros:
          type: number
          example: 50000000
          description: Teto em micro-USD (50 USD).
        spentMicros:
          type: number
          example: 12400000
          description: Gasto acumulado, em micro-USD.
        policy:
          type: string
          enum:
            - block
            - allow
          example: block
          description: >-
            `block` recusa a chamada que estouraria o teto; `allow` a deixa
            passar e só avisa.
        lastThresholdNotified:
          type: number
          example: 70
          description: >-
            Maior limiar (70/90/100) já notificado. É ele, e não o gasto
            anterior, que serve de piso — usar o gasto faria o mesmo aviso
            disparar de novo em toda chamada depois de cruzado.
        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` (padrão) para quem opera o sistema; `external_system` para
            outro sistema com que ele conversa (ex.: um provider de 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: Sintaxe Mermaid `C4Context` — o sistema e os atores externos.
        containerDiagram:
          type: string
          example: |-
            C4Container
              title Container Diagram -- Brabo
              ...
          description: >-
            Sintaxe Mermaid `C4Container` — os módulos do module_map vigente e
            as dependências entre eles.
      required:
        - systemName
        - systemDescription
        - actors
        - contextDiagram
        - containerDiagram
    ChatSseEventResponseDto:
      type: object
      properties:
        type:
          type: string
          enum:
            - delta
            - done
            - error
            - metering_failed
          example: delta
          description: >-
            `delta` leva `text` incremental; `done` fecha com a contabilidade de
            tokens/custo; `error` aborta; `metering_failed` quer dizer que a
            RESPOSTA saiu mas o custo não foi contabilizado — o quadro existe
            para essa falha não passar despercebida.
        text:
          type: string
          example: I will open the epic
          description: Só em `delta`.
        inputTokens:
          type: number
          example: 1820
          description: Só em `done`.
        outputTokens:
          type: number
          example: 340
          description: Só em `done`.
        costMicros:
          type: number
          example: 52700
          description: Só em `done`, micro-USD.
        estimated:
          type: boolean
          example: false
          description: >-
            Só em `done`. `true` quando o provider não devolveu contagem e o
            custo foi ESTIMADO — o número vale menos e a interface precisa poder
            dizer isso.
        message:
          type: string
          example: session budget exhausted
          description: Só em `error` e `metering_failed`.
      required:
        - type
    CicloDeVidaDoContainerResponseDto:
      type: object
      properties:
        status:
          type: string
          enum:
            - provisioning
            - running
            - stopped
            - failed
            - removed
          example: provisioning
          description: >-
            O que foi REGISTRADO, não o que um daemon do Docker confirma —
            nenhum orquestrador real transiciona esta tabela hoje (RN-243).
        imageVersion:
          type: number
          example: 1
          description: >-
            Versão do `artifact.project_image` CONGELADA na primeira transição
            (RN-245) — não reaplicada a cada leitura.
        resources:
          description: Teto DECLARADO no provisionamento — não aplicado (RN-248).
          allOf:
            - $ref: '#/components/schemas/RecursosDoContainerResponseDto'
        failureReason:
          type: object
          nullable: true
          example: null
          description: Só preenchido numa transição para `failed`.
        createdAt:
          type: string
          format: date-time
        statusChangedAt:
          type: string
          format: date-time
        observado:
          nullable: true
          description: >-
            O que o daemon do Docker relata AGORA, perguntado pelo broker (ADR
            0130) — nunca fundido com os campos acima, que são o que foi
            REGISTRADO. Um container morto por fora aparece como `running`
            registrado e `exited` observado, e é esse o ponto. `null` quer dizer
            "não há container" ou "não consegui olhar" — leia `naoObservado`
            para distinguir.
          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` quando a observação de fato ACONTECEU — inclusive quando
            voltou vazia, que é a afirmação positiva "olhei e não há container".
            Preenchido quando não houve como olhar, dizendo por quê. Herdar aqui
            o estado registrado é exatamente o que a RN-468 proíbe.
        detalheDaObservacao:
          type: object
          nullable: true
          example: null
          description: A mensagem do próprio broker, quando houve.
      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: Só tarefas deste módulo entram no sorteio.
        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: Começando em 1, como todo editor mostra.
        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: O arquivo passou do teto de linhas anotadas por chamada.
      required:
        - ref
        - path
        - lines
        - truncated
    CodeBranchDetailListResponseDto:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/CodeBranchDetailResponseDto'
        truncated:
          type: boolean
          example: false
          description: A lista foi cortada no teto de branches enriquecidas.
      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: Commits à frente da branch padrão. `null` quando não é calculável.
        behind:
          type: number
          nullable: true
          example: 0
          description: Commits atrás da branch padrão.
        pullRequest:
          nullable: true
          type: object
          allOf:
            - $ref: '#/components/schemas/CodeBranchPullRequestRefResponseDto'
        producedBy:
          nullable: true
          description: >-
            Dev agent/módulo dono da branch (RN-152), quando o nome casa com
            `feature/task-XXXXXXXX` e a tarefa/módulo ainda são resolvíveis.
            `null` para branch manual de um usuário ou para `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 do dev que criou a branch (`dev-<module>`/`dev-<module>-2`,
            RN-087).
        moduleId:
          type: string
          example: pieces
          description: Nome do módulo, do `module_map` vigente do projeto.
      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: Caminho DEPOIS da mudança (para `removed`, o que sumiu).
        previousPath:
          type: string
          nullable: true
          example: null
          description: Caminho anterior; só preenchido quando `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: >-
            O diff unificado do arquivo. `null` quando o provider não o entrega
            (binário, ou patch grande demais) — distinto de `""`, que é "voltou
            vazio". Sem essa distinção a tela diria "sem mudanças" num binário
            alterado.
      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: A lista foi cortada no teto de arquivos por 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: Conteúdo UTF-8. Binário não é servido por esta rota.
        truncated:
          type: boolean
          example: false
          description: >-
            O arquivo passou do teto de bytes e `content` é o começo dele. A aba
            é somente leitura: cortar e avisar é melhor que recusar o arquivo.
        bytes:
          type: number
          example: 1284
          description: Bytes DEVOLVIDOS — depois do corte, não antes.
      required:
        - ref
        - path
        - content
        - truncated
        - bytes
    CodePullRequestListResponseDto:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/CodePullRequestSummaryResponseDto'
        truncated:
          type: boolean
          example: false
          description: A lista foi cortada no teto de PRs por chamada.
      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: Login/username de quem abriu. `null` quando o provider não informa.
        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` quando o provider não informa.'
      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: Começando em 1, como todo editor mostra.
        text:
          type: string
          example: '  const app = await NestFactory.create(AppModule);'
          description: >-
            A linha inteira, aparada para um arquivo minificado nunca ser
            devolvido inteiro.
      required:
        - path
        - line
        - text
    CodeSearchResponseDto:
      type: object
      properties:
        ref:
          type: string
          example: dev
        path:
          type: string
          example: ''
          description: Subárvore sobre a qual a busca rodou.
        query:
          type: string
          example: NestFactory
        matches:
          type: array
          items:
            $ref: '#/components/schemas/CodeSearchMatchResponseDto'
        filesScanned:
          type: number
          example: 87
          description: >-
            Arquivos de fato abertos. É o custo real da busca, e está na
            resposta para não ficar invisível.
        truncated:
          type: boolean
          example: true
          description: >-
            A varredura parou por orçamento (diretórios, arquivos ou acertos)
            antes de terminar a árvore. Estreite o `path` ou o termo.
      required:
        - ref
        - path
        - query
        - matches
        - filesScanned
        - truncated
    CodeTreeEntryResponseDto:
      type: object
      properties:
        path:
          type: string
          example: apps/api/src/main.ts
          description: Caminho completo a partir da raiz do repositório.
        name:
          type: string
          example: main.ts
          description: Último segmento de `path`.
        type:
          type: string
          enum:
            - file
            - dir
          example: file
        size:
          type: number
          nullable: true
          example: 1284
          description: >-
            Bytes. `null` para diretório e quando o provider não informa —
            distingui-lo de `0` importa para a tela nunca afirmar "arquivo
            vazio".
      required:
        - path
        - name
        - type
        - size
    CodeTreeResponseDto:
      type: object
      properties:
        ref:
          type: string
          example: dev
          description: Branch, tag ou sha lido.
        path:
          type: string
          example: apps/api
          description: Diretório listado; `""` é a raiz.
        entries:
          type: array
          items:
            $ref: '#/components/schemas/CodeTreeEntryResponseDto'
        truncated:
          type: boolean
          example: false
          description: >-
            A listagem foi cortada no teto de entradas por nível. Há mais no
            diretório do que voltou aqui.
      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: >-
            SOMADO às regras que a história já cita (completar nunca desliga uma
            regra); cada id novo tem de referenciar um `artifact.business_rule`
            existente.
          type: array
          items:
            type: string
        questionId:
          type: string
          format: uuid
          description: >-
            RN-859 (ADR 0214): uma pergunta aberta que o usuário JÁ respondeu —
            só então uma história `ready` (sem tarefa em execução) pode ser
            corrigida.
      required:
        - projectId
    ConfirmacaoDeArquiteturaResponseDto:
      type: object
      properties:
        ok:
          type: boolean
          example: true
          enum:
            - true
        desfecho:
          type: string
          enum:
            - confirmado
            - ja_oferecido
          description: >-
            `confirmado`: a Infra foi disparada. `ja_oferecido`: ela já tinha
            oferta pendente ou estava ativa no projeto — nada foi gravado nem
            pedido ao engine (duplo clique, segunda aba). O Dev Lead não é mais
            alvo aqui: a Infra o oferece assim que o container está `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: >-
            Caminho absoluto confirmado pelo runner NO HOST — a fonte de verdade
            (RN-423). Revalidado LEXICAMENTE aqui antes de gravar; inválido é
            400, nunca gravado.
        sessionId:
          type: string
          format: uuid
          example: 01JC4Z0000SESSAO00000000001
          description: >-
            A sessão mais recente do projeto (`ProjectSession.latest_id/1`),
            resolvida do lado do engine. `null`/omitida quando o projeto ainda
            não tem sessão nenhuma — o projeto é atualizado assim mesmo, só o
            evento de auditoria é pulado (lacuna aceita, RN-423).
        actorId:
          type: string
          format: uuid
          example: 01JC4Z0000USUARIO0000000001
          description: >-
            Quem pediu o ticket do runner (dono do socket que confirmou) — vira
            o ator do evento `project.workspace_verified`. Omitido: o evento (se
            houver sessão) nasce sem ator identificado.
      required:
        - path
    ConfirmProjectWorkspaceResponseDto:
      type: object
      properties:
        verified:
          type: boolean
          example: true
          description: >-
            Sempre `true` quando a chamada não lançou — a confirmação é
            idempotente. `changed` diz se ALGO mudou nesta chamada.
        workspacePath:
          type: string
          example: /home/you/projects/store
          description: >-
            O caminho GRAVADO (normalizado) — pode diferir do que a criação
            tinha, porque o runner é a fonte de verdade e o sobrescreve.
        changed:
          type: boolean
          example: true
          description: >-
            `true` na primeira confirmação, ou quando o caminho relatado é
            DIFERENTE do que já estava gravado. `false` numa reconexão que
            relata o mesmo caminho de sempre — nada foi regravado.
      required:
        - verified
        - workspacePath
        - changed
    ContainerExecInternalDto:
      type: object
      properties:
        comando:
          type: string
          example: npm test
          description: >-
            O comando de shell, interpretado por `sh -c` DENTRO do container — a
            mesma convenção da ação de terminal rodada fora de um.
        cwd:
          type: string
          example: /work/.worktrees/dev-api
          description: >-
            Diretório de trabalho DENTRO do container. Já traduzido do caminho
            do host pelo engine (RN-492) — este DTO não repete essa tradução.
            Omitido roda em `/work`, a raiz do projeto. O broker revalida que
            está dentro de `/work` e recusa (`DiretorioForaDoEscopoError`) caso
            contrário.
        timeoutMs:
          type: number
          example: 300000
          description: Timeout em milissegundos. Omitido usa o padrão do 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` quando o broker recusou ou não respondeu — os outros campos
            ficam ausentes e `motivo` explica.
        exitCode:
          type: number
          example: 0
          description: 'Presente só quando `sucesso: true`.'
        output:
          type: string
          example: |
            ok
          description: 'Presente só quando `sucesso: true`.'
        timedOut:
          type: boolean
          example: false
          description: 'Presente só quando `sucesso: true`.'
        motivo:
          type: string
          example: 'o broker de container não respondeu em http://broker:8090: timeout'
          description: 'Presente só quando `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: >-
            Onde o container deste projeto sobe (RN-497/503). `container` e
            `mounted` passam pelo broker, no servidor; `runner` passa pelo
            agente local na máquina do usuário. É o que ramifica a ação de
            subida entre `container_start` e `container_start_via_runner`.
        registrado:
          nullable: true
          description: >-
            `null` quer dizer que este projeto NUNCA provisionou container — um
            TERCEIRO estado, não um valor de `status`: não é `stopped`
            (container que existiu e parou) nem "não pôde ser observado" (que é
            sobre o daemon). Leia `naoVerificado: "sem_container_registrado"`
            junto.
          type: object
          allOf:
            - $ref: '#/components/schemas/RegistroDeContainerResponseDto'
        temImagemDecidida:
          type: boolean
          example: true
          description: >-
            Se existe ALGUM `artifact.project_image` para este projeto — o
            portão da RN-105, que desde a RN-494/ADR 0135 vale para os TRÊS
            modos. `false` quer dizer que subir container é impossível agora, e
            a página diz isso em vez de propor uma ação que se sabe que vai
            falhar.
        workspaceVerifiedAt:
          type: object
          nullable: true
          format: date-time
          description: >-
            Quando um runner CONFIRMOU a pasta (RN-423) — só tem sentido para
            `executionMode: "runner"`. Registra UMA confirmação e nunca é
            batimento (RN-468): não nulo NÃO prova que um agente local está
            conectado agora. O que ele prova é o negativo — `null` num projeto
            `runner` quer dizer que nenhum runner jamais conectou.
        observado:
          nullable: true
          description: >-
            O que o broker relata agora — `null` porque não há container, porque
            não foi possível perguntar (`naoObservado`), ou porque esta linha
            não foi perguntada nesta carga (`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: >-
            Não nulo quando esta linha NÃO foi perguntada ao broker nesta carga
            — nunca confundido com `naoObservado`, que quer dizer que FOI
            perguntada e falhou. `fora_do_escopo_da_verificacao`: status é
            `stopped`/`failed`/`removed`, onde a confirmação do daemon não
            importa. `teto_de_verificacoes_atingido`: elegível, mas o orçamento
            de chamadas ao broker por carga já foi gasto por outras linhas.
            `sem_container_registrado`: o projeto nunca provisionou um, então
            não há o que observar — e também nunca gasta chamada do orçamento.
        acaoPendente:
          nullable: true
          description: >-
            A ação pendente
            `container_start`/`container_stop`/`container_remove`/`container_start_via_runner`
            deste projeto, se houver — em QUALQUER sessão dele. A página desenha
            o `ApprovalCard` em linha para ela em vez do botão da ação, o mesmo
            padrão da aba de PRs.
          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: >-
            Se ESTA INSTALAÇÃO tem broker de container configurado (`BROKER_URL`
            definida, ADR 0130) — o mesmo valor em toda linha, porque é
            configuração da instalação e não propriedade do projeto. Projetos
            `container` e `mounted` só sobem container pelo broker (ADR 0144),
            então com `false` a página recusa o botão de subida para eles ANTES
            do clique e diz por quê (ADR 0161, RN-574); projetos `runner` não
            são afetados. Diz que a variável existe, nunca que o broker responde
            — isso é o que `naoObservado` relata.
      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: >-
            Nome da pasta CONGELADO na criação do projeto (RN-109). É a fonte
            única do nome do container (`brabo-<workspaceDirName>`) nos TRÊS
            modos. ONDE a PASTA está é outra pergunta, respondida por
            `localizacao` (RN-503) — a api continua nunca mandando caminho
            absoluto, porque um caminho de dentro do container da api não é um
            caminho que o daemon do Docker consiga resolver.
        executionMode:
          type: string
          enum:
            - container
            - mounted
            - runner
          example: container
          description: >-
            Onde o código mora. O broker serve `container` E `mounted` (RN-503):
            desde o ADR 0141 a pasta montada mora sob uma base que este servidor
            monta por identidade, então o daemon a alcança. Continua recusando
            `runner`: essa pasta está na máquina do usuário e nenhuma raiz aqui
            a enxerga — ali, quem sobe container é o runner (ADR 0137).
        localizacao:
          description: >-
            O localizador discriminado da pasta do projeto (RN-503): qual das
            duas raízes do broker o resolve, e o segmento relativo a juntar a
            essa raiz.
          allOf:
            - $ref: '#/components/schemas/LocalizacaoDoProjetoResponseDto'
        usuarioDaPasta:
          type: object
          nullable: true
          properties:
            uid:
              type: integer
              example: 1000
            gid:
              type: integer
              example: 1000
          description: >-
            O DONO da pasta do projeto, medido pela api (ADR 0180): o broker
            sobe o container com `--user uid:gid` para o dev agent conseguir
            escrever na pasta sob `--cap-drop ALL`. `null` quando a pasta não
            pode ser medida, o projeto é `runner`, ou o dono é root — o
            container então sobe como antes. Nunca aceito de um pedido ao
            broker.
        imagem:
          nullable: true
          description: >-
            `null` enquanto o Arquiteto não decidiu (RN-105) — `start` é então
            recusado com 409 do lado do broker, e as outras quatro operações
            continuam funcionando.
          type: object
          allOf:
            - $ref: '#/components/schemas/ImagemParaOBrokerResponseDto'
        imagemVersao:
          type: number
          example: 3
          description: >-
            Versão do artefato vigente; 0 quando não há decisão. Vai parar no
            container como o rótulo `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: Nome do módulo — tem de existir no module_map vigente.
        expoe:
          description: O que o módulo expõe a quem depende dele. De 1 a 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: 'O `dependsOn` do module_map vigente: o que este módulo CONSOME.'
          type: array
          items:
            type: string
        expoe:
          nullable: true
          description: >-
            O que ele expõe. `null` = o Arquiteto não declarou contrato para
            este módulo (diferente de não expor nada).
          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 quando não há contrato.
        modulos:
          description: Um por módulo do module_map VIGENTE, na ordem do mapa.
          type: array
          items:
            $ref: '#/components/schemas/ContratoLidoDeModuloResponseDto'
        contratosForaDoMapa:
          example: []
          description: >-
            Contratos de módulos que o module_map vigente não tem mais — ditos,
            nunca presos a um módulo que não existe.
          type: array
          items:
            type: string
      required:
        - status
        - version
        - modulos
        - contratosForaDoMapa
    ConvertExecutionModeDto:
      type: object
      properties:
        executionMode:
          type: string
          enum:
            - container
            - mounted
            - runner
          example: runner
          description: >-
            O novo modo de execução. Pode repetir o modo atual do projeto —
            nesse caso, só `workspacePath` muda de verdade.
        workspacePath:
          type: string
          example: /home/you/projects/store
          description: >-
            O caminho absoluto da pasta do usuário — obrigatório quando
            `executionMode` é `mounted` ou `runner`, recusado quando é
            `container`. Mesma validação da criação (RN-170/RN-422/RN-423): em
            `mounted`, precisa existir e ser gravável de dentro do container; em
            `runner`, só o formato é validado agora — a confirmação de verdade
            vem do runner conectando de novo.
      required:
        - executionMode
    CoverageReportResponseDto:
      type: object
      properties:
        rules:
          type: array
          items:
            $ref: '#/components/schemas/RuleCoverageResponseDto'
        uncoveredCount:
          type: number
          example: 2
          description: Quantas regras não têm história nenhuma. Cada uma é uma descoberta.
      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: Sempre um agente nesta rota.
          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: Nome do sistema/projeto, para o rótulo do diagrama.
        systemDescription:
          type: string
          example: Agent-orchestrated engineering platform.
        actors:
          description: >-
            Atores externos do nível Context (Simon Brown). Os containers do
            nível Container NÃO entram aqui: vêm do module_map vigente do
            projeto.
          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: Requisitos funcionais.
          type: array
          items:
            type: string
        rnf:
          example:
            - The response stays under 200 ms at p95
          description: Requisitos não funcionais.
          type: array
          items:
            type: string
        businessRuleIds:
          example:
            - RN-014
          description: >-
            Regras de negócio cobertas. É por aqui que a cobertura
            regra→história é calculada em `GET /projects/:id/coverage`.
          type: array
          items:
            type: string
        dod:
          example:
            - Unit tests green
          description: Definição de pronto.
          type: array
          items:
            type: string
        dor:
          example:
            - Module defined
          description: Definição de preparado.
          type: array
          items:
            type: string
        moduleIds:
          example:
            - api
          description: >-
            Módulos do `module_map` que a história toca. História sem módulo, ou
            com módulo inexistente, vira pendência de arquitetura.
          type: array
          items:
            type: string
        status:
          type: string
          enum:
            - draft
            - ready
            - in_progress
            - done
          example: ready
        proposedReady:
          type: boolean
          example: true
          description: >-
            O PO terminou a história e ela espera a decisão do usuário (Fase 12c
            — RN-048). Convive com `status: "draft"`: é uma proposta, não um
            estado. Sempre `false` num projeto no modo `auto`.
        returnedReason:
          type: object
          example: DoD too generic — spell out the acceptance criteria.
          nullable: true
          description: >-
            Por que o usuário devolveu a história ao PO. `null` quando nunca foi
            devolvida.
        returnedAt:
          type: object
          example: '2026-08-02T14:00:00.000Z'
          format: date-time
          nullable: true
        archivedAt:
          type: string
          example: null
          format: date-time
          description: >-
            Quando a história foi ARQUIVADA (RN-727). Histórias arquivadas saem
            do backlog, da cobertura, do plano e do claim; `null` é uma história
            em jogo.
          nullable: true
        archivedReason:
          type: string
          example: null
          description: Por que foi arquivada, quando alguém disse (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 do session_event do artefato que motivou o handoff.
        seAusente:
          type: boolean
          example: true
          description: >-
            Só se ninguém o pegou ainda (ADR 0182, RN-636): com uma oferta
            pendente ao alvo em QUALQUER sessão do projeto, devolve-a
            (`desfecho: ja_oferecido`) em vez de substituí-la. O AppSec o manda.
      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: >-
            Grafo de módulos. Um CICLO faz o mapa ser recusado com 400 — a
            validação é de domínio. Cada módulo pode declarar `resources` (cpus,
            memoryMb, pidsLimit — os três ou nenhum); declaração parcial, valor
            acima do teto do container ou SOMA dos módulos acima dele também é
            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. Único dentro do workspace.
        maxConsecutiveBlocked:
          type: number
          example: 3
          description: >-
            Circuit breaker (Fase 12b — RN-047): quantas tarefas consecutivas
            terminando bloqueadas param o dev agent do módulo em idle_tripped.
            Omitido usa o padrão do domínio (DEFAULT_MAX_CONSECUTIVE_BLOCKED).
            Vale da PRÓXIMA ativação de execução em diante — não afeta agentes
            já rodando.
        storyPromotion:
          type: string
          enum:
            - manual
            - auto
          example: auto
          description: >-
            Quem promove uma história de `draft` para `ready` (Fase 12c —
            RN-048). `auto` (padrão de projeto novo desde a RN-659): história
            completa já nasce `ready`, sem passo humano. `manual`: o PO propõe e
            VOCÊ decide, na aba Backlog. Mudar o padrão não reescreveu projeto
            existente nenhum. As validações de domínio
            (DoD/DoR/RF/regra/módulos) são as MESMAS nos dois modos; o modo só
            muda quem dispara.
        executionMode:
          type: string
          enum:
            - container
            - mounted
            - runner
          example: container
          description: >-
            ONDE o comando deste projeto EXECUTA (RN-169/RN-421 — ADR
            0072/0104). `container` (padrão): a pasta gerenciada pelo produto
            dentro de PROJECT_WORKSPACES_ROOT, o comportamento de sempre.
            `mounted`: uma pasta SUA, dada em `workspacePath`, dentro da base da
            instalação `BRABO_PROJECTS_BASE` (ADR 0141) — a criação valida só o
            FORMATO do caminho mais essa base, e a pasta em si é criada depois,
            quando a Infra sobe o container (RN-501, ADR 0142). `runner`: uma
            pasta SUA que NÃO precisa de bind-mount — a criação só valida o
            formato do caminho e o projeto nasce "não verificado"; rode
            `brabo-runner --project <id> --dir <folder>` na sua máquina para
            confirmá-lo (RN-423).
        workspacePath:
          type: string
          example: /home/you/projects/store
          description: >-
            O caminho ABSOLUTO da pasta, obrigatório quando `executionMode` é
            `mounted` ou `runner`, e recusado quando é `container`. Nos DOIS só
            o FORMATO é validado agora — absoluto, sem `..`, não a raiz do
            sistema, não pasta de sistema, não sobreposto ao checkout do Brabo
            (RN-422/RN-423). `mounted` acrescenta uma regra: tem de ficar dentro
            de `BRABO_PROJECTS_BASE`, a única pasta da sua máquina que os
            containers do Brabo enxergam (ADR 0141) — sem base configurada o
            modo não está disponível nesta instalação e a criação diz isso. O
            disco é tocado depois: pelo runner em `runner`, e quando a Infra
            sobe o container em `mounted` (RN-501, ADR 0142).
        language:
          type: string
          example: pt-BR
          maxLength: 35
          description: >-
            O idioma do PROJETO (RN-619) — artefatos compartilhados e turnos sem
            autor humano o usam. Qualquer código BCP-47 que o servidor
            reconhece, gravado na forma canônica; um não reconhecido é 400.
            Omitido, o projeto começa com o idioma de resposta efetivo de quem o
            criou (RN-618: escolha da conta > detecção confirmada > idioma da
            interface).
      required:
        - name
        - slug
    CreateSessionDto:
      type: object
      properties:
        kind:
          type: string
          enum:
            - consultiva
            - criativa
          example: criativa
          description: >-
            A intenção da sessão. `consultiva` é só conversa; `criativa` é a que
            produz — abre a ideação com o agente Criativo e é a única que entra
            em execução. OBRIGATÓRIO: sem ele a resposta é 400, porque o tipo é
            escolha de quem abre e fica gravado.
        name:
          type: string
          example: Cart checkout
          maxLength: 80
          description: >-
            Nome amigável, opcional. Não substitui a hashtag do id — as telas
            compõem os dois. Em branco conta como ausente.
      required:
        - kind
    CreateSocketTicketDto:
      type: object
      properties:
        scope:
          type: string
          enum:
            - heartbeat
            - terminal
          example: heartbeat
          description: >-
            `heartbeat` exige o papel `viewer`; `terminal` exige `developer` — o
            mesmo papel mínimo de `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: >-
            Cada id precisa referenciar um evento `artifact.business_rule` que
            EXISTE — id inventado é recusado.
          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. Único no sistema e visível na 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: >-
            A credencial existe HOJE. `false` é gasto histórico de uma chave que
            já foi removida — o consumo aconteceu e não some do relatório por
            causa disso.
        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: >-
            Parcela gasta por AGENTES — a conta que a RN-058 passou a cobrar do
            owner.
        costMicrosPessoas:
          type: number
          example: 350000
          description: >-
            Parcela gasta por PESSOAS no chat. Sai da mesma chave, e é outra
            pergunta.
        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: Dono das chaves — quem financia os agentes deste workspace (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` — o provider aceitou a credencial. `recusado` — o provider a
            rejeitou (chave inválida/revogada, sem saldo, rede). `nao_suportado`
            — este provider não tem endpoint de teste verificado (`ollama`,
            `anthropic`, `openai`): NADA foi verificado, e dizer "ok" aqui seria
            mentira.
        motivo:
          type: string
          example: 'connection test failed for openrouter: openrouter responded 401'
          description: Só em `recusado` — o motivo que o provider deu.
      required:
        - resultado
    CriterioDoAceiteResponseDto:
      type: object
      properties:
        regras:
          type: number
          example: 4
          description: Regras de negócio no projeto.
        cobertas:
          type: number
          example: 4
          description: Regras citadas por ao menos uma história — igual a `regras`.
        repositorio:
          type: string
          enum:
            - local
            - a_provisionar_local
          example: a_provisionar_local
          description: >-
            `local`: o projeto já tinha um repositório `local`.
            `a_provisionar_local`: nenhum ainda — o aceite provisiona um
            `local`.
      required:
        - regras
        - cobertas
        - repositorio
    DecideBootstrapPlanDto:
      type: object
      properties:
        planGeneratedAt:
          type: string
          example: '2026-08-01T23:45:00.000Z'
          format: date-time
          description: >-
            O `generatedAt` do plano que você VIU. Trava otimista: se o plano
            foi regenerado desde então (readoção, ou o repositório mudou), a
            decisão é recusada com 409 em vez de aplicar um sim dado sobre outra
            coisa.
      required:
        - planGeneratedAt
    DecideBootstrapPlanResponseDto:
      type: object
      properties:
        repository:
          $ref: '#/components/schemas/ProvisionedRepositoryResponseDto'
        bootstrap:
          description: >-
            Onde o bootstrap foi parar. No `skip` o cursor NÃO avança: nenhum
            passo rodou, e o registro diz isso.
          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: >-
            Referência OCI com TAG ou digest explícito. `latest` é RECUSADO com
            400 — a regra é de domínio.
        rationale:
          type: string
          example: >-
            The module_map is TypeScript on Node 22; the slim variant has the
            runtime and nothing else.
          description: Por que esta imagem. Mínimo de 10 caracteres.
        network:
          type: string
          enum:
            - none
            - egress
          default: none
          description: >-
            A postura de rede do container. `none` é o padrão, e é o que torna
            "lá dentro, o agente é livre" uma frase segura.
        resources:
          type: object
          additionalProperties: true
          example:
            cpus: 2
            memoryMb: 4096
            pidsLimit: 512
          description: >-
            Teto de recursos. Acima do máximo, a decisão é RECUSADA, nunca
            rebaixada em silêncio.
      required:
        - projectId
        - image
        - rationale
    DecisaoDeImagemResponseDto:
      type: object
      properties:
        image:
          type: string
          example: node:22-bookworm-slim
          description: >-
            Referência OCI com TAG ou digest explícito. `latest` é recusado: o
            artefato precisa dizer a mesma coisa daqui a seis meses.
        rationale:
          type: string
          example: >-
            The module_map is all TypeScript over Node; the slim variant is
            enough and reduces surface.
          description: Por que ESTA imagem. É o que torna a decisão revisável.
        network:
          type: string
          enum:
            - none
            - egress
          example: none
          description: >-
            A postura de rede do container, decidida UMA vez no artefato e não
            comando a comando. `egress` é custo e superfície: o Arquiteto pede,
            o usuário autoriza no provisionamento.
        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: >-
            A lista INTEIRA de contratos: cada chamada é uma versão nova que
            substitui a anterior. O que um módulo CONSOME não está aqui — é
            derivado do `dependsOn` do module_map vigente na leitura.
          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 quando a área não tem tarefa de backlog por trás (Infra, Fase
            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: Fica no event log junto com a recusa. Opcional, mas recomendado.
    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: >-
            Sinalizado pelo Arquiteto. Vira um checklist informativo no veredito
            do SecOps — não há correlação linha a linha, e afirmar que houvesse
            distorceria a profundidade da checagem.
      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: A pergunta que foi decidida.
        decisao:
          type: string
          description: A decisão do usuário.
        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: >-
            ADRs que se aplicam: os transversais (sem módulo declarado) mais os
            que citam o módulo deste agente.
          type: array
          items:
            $ref: '#/components/schemas/DevContextAdrResponseDto'
        siblingTasks:
          description: >-
            As outras tarefas da mesma história, com o status delas. O gate de
            QA julga a entrega pelo que é DESTA tarefa; requisito de uma tarefa
            irmã é observação, nunca reprovação (RN-765).
          type: array
          items:
            $ref: '#/components/schemas/DevContextSiblingTaskResponseDto'
        moduleOpenTasks:
          description: >-
            Tarefas de OUTRAS histórias do mesmo módulo ainda não concluídas,
            com o título da história (no máximo 20). Requisito que depende do
            que outra história vai entregar é observação para o gate de QA,
            nunca reprovação (RN-785).
          type: array
          items:
            $ref: '#/components/schemas/DevContextModuleOpenTaskResponseDto'
        moduleOpenTasksTotal:
          type: number
          description: >-
            Número real de tarefas abertas do módulo em outras histórias — maior
            que `moduleOpenTasks.length` quando a lista foi cortada.
        moduleContract:
          nullable: true
          description: >-
            O contrato vigente do módulo da tarefa (`artifact.module_contracts`)
            — a fonte da INTERFACE para o gate de QA. Quando a história e o
            contrato divergem, o veredito nomeia a divergência em vez de
            reprovar o dev que seguiu o contrato (RN-786). `null` sem módulo ou
            contrato resolvível.
          type: object
          allOf:
            - $ref: '#/components/schemas/ContratoDeModuloResponseDto'
        primeiraDoModulo:
          type: boolean
          description: >-
            Se esta é a PRIMEIRA tarefa do módulo: o `backlog.task_claimed` mais
            antigo do projeto para `module` é esta tarefa. O engine lhe dá o
            dobro do orçamento por tarefa (RN-774). `false` sem `module`.
        decisoes:
          description: >-
            RN-859: as decisões do usuário ligadas a esta tarefa, à história
            dela ou às regras dela por `afeta` — vencem RF/DoR em conflito. A
            mais nova primeiro, no máximo 10, cada texto cortado em 1.500
            caracteres.
          type: array
          items:
            $ref: '#/components/schemas/DevContextDecisaoResponseDto'
        decisoesDoProjeto:
          description: >-
            RN-872 (ADR 0215): as OUTRAS decisões do projeto — aquelas cujo
            `afeta` não toca esta tarefa. Seção separada, dentro do MESMO teto
            de 10 de `decisoes` (as decisões da tarefa ocupam o teto primeiro),
            a mais nova primeiro.
          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` é o estado inicial: o Arquiteto ainda não gerou um.'
        diagrama:
          nullable: true
          description: O diagrama vigente, ou `null` enquanto não há.
          type: object
          allOf:
            - $ref: '#/components/schemas/C4DiagramaResponseDto'
        version:
          type: number
          example: 0
          description: >-
            Versão do artefato vigente; 0 quando não há diagrama. Revisar é
            gerar de novo — o histórico nunca é reescrito.
        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` é o estado inicial de todo projeto e é o que fecha o
            portão da RN-105: sem imagem não há container, e a aba Código não
            abre.
        decisao:
          nullable: true
          description: A decisão vigente, ou `null` quando ainda não há.
          type: object
          allOf:
            - $ref: '#/components/schemas/DecisaoDeImagemResponseDto'
        version:
          type: number
          example: 0
          description: >-
            Versão do artefato vigente; 0 quando não há decisão. Revisar é
            emitir versão nova — o histórico nunca é reescrito.
        eventId:
          type: object
          nullable: true
          example: 01JC4Z0000EVENTO000000001
          description: Id do evento que fixou a decisão vigente, para auditoria.
        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: Sessão de execução criada.
        modules:
          example:
            - api
            - web
          description: >-
            Módulos do `module_map` que ganharam dev agent. Um agente por
            módulo, cada um num worktree isolado.
          type: array
          items:
            type: string
      required:
        - sessionId
        - modules
    FirstAccountInternalDto:
      type: object
      properties:
        email:
          type: string
          example: voce@exemplo.dev
          description: >-
            O e-mail do owner, digitado no TTY. Normalizado antes de qualquer
            gravação; é o que o instalador grava no marcador dele, já que e-mail
            identifica e não é segredo.
        senha:
          type: string
          example: uma frase longa e minha
          description: >-
            Lida sem eco e confirmada pelo instalador. Usada e descartada —
            nunca gravada no `.env`, no marcador nem em log, e nunca gerada por
            código nenhum: não há caminho em que uma conta nasça com senha
            escolhida pelo script. O mínimo é a política do DOMÍNIO, o mesmo
            código que o cadastro usa; o tamanho máximo aqui é proteção do
            argon2id, não política.
        nome:
          type: string
          example: Fulana de Tal
          description: >-
            Nome de exibição. Omitido: o nome do workspace pessoal cai na parte
            local do e-mail (RN-410) — nada é inventado.
      required:
        - email
        - senha
    FirstAccountInternalResponseDto:
      type: object
      properties:
        userId:
          type: string
          format: uuid
          example: 01JC4Z0000USUARIO0000000001
          description: O usuário que foi criado.
        email:
          type: string
          example: voce@exemplo.dev
          description: >-
            O e-mail NORMALIZADO — o que foi de fato gravado, que pode diferir
            do digitado. É o que o instalador grava no marcador dele.
        workspaceId:
          type: string
          format: uuid
          example: 01JC4Z0000WORKSPACE00000001
          description: >-
            O workspace pessoal nascido na MESMA transação (RN-410), com a conta
            como `owner`. Sem ele a instalação fecharia com um login que
            funciona e um painel onde "Novo projeto" não tem onde criar.
      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: >-
            A decisão é do usuário — seja por clique direto, seja delegada por
            uma política que ele escreveu. Invariante nos quatro gates
            constitucionalmente manuais (RN-071).
        status:
          type: string
          enum:
            - active
            - planned
        evidencia:
          type: object
          additionalProperties: true
          description: >-
            Onde mora a prova de que o gate passou: `event_log` (tipos + filtro
            de payload), `teste` ou `ci` (caminho do alvo). Ausente num 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: Fluxo a que o gate pertence.
        dono:
          type: string
          example: area-qa
          description: Quem julga.
        entrada:
          example:
            - pr-aberta
          description: O que precisa existir para o gate abrir.
          type: array
          items:
            type: string
        entregavel:
          type: object
          example: veredito-qa
          description: O que o gate produz quando passa.
        aprovacaoHumana:
          type: boolean
          example: true
          description: >-
            A decisão é do usuário — direta no clique OU delegada por uma
            política que ele mesmo escreveu (ver 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: Versão do formato do registro.
        gates:
          description: >-
            Só gates ATIVOS. Um gate `planned` descreve um papel futuro e não
            deveria aparecer numa tela que diz o que está acontecendo agora.
          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: >-
            O que acontece a seguir. `correct` volta ao dev; `run_secops` avança
            o gate; `done` libera para o usuário; `await_user` estaciona o ciclo
            até a pergunta aberta ser respondida (RN-860); `blocked` quer dizer
            que o teto de correções acabou e só um humano pode desbloquear.
        task:
          $ref: '#/components/schemas/TaskResponseDto'
        openQuestionId:
          type: string
          format: uuid
          description: 'Com `await_user`: a pergunta aberta pela qual o ciclo espera.'
      required:
        - nextAction
        - task
    GitAuthorizeUrlResponseDto:
      type: object
      properties:
        authorizeUrl:
          type: string
          example: >-
            https://github.com/login/oauth/authorize?client_id=…&state=…&redirect_uri=…
          description: >-
            Para onde mandar o navegador. O `state` é assinado por HMAC com
            `GIT_OAUTH_STATE_SECRET` — é isso que impede o callback de ser
            forjado.
      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 do agente que passou o bastão.
        toAgent:
          type: string
          example: po
          description: Slug do agente que recebe.
        artifactId:
          type: object
          example: 01JC4Z0000ARTEFATO000000001
          nullable: true
          description: Artefato que motivou o handoff (product_brief, module_map…).
        status:
          type: string
          enum:
            - offered
            - accepted
            - completed
            - rejected
            - superseded
          example: offered
          description: >-
            Este campo é MUTÁVEL — é o estado corrente. Cada transição também
            vira um evento `handoff.*` imutável no log, que é onde o histórico
            mora. `superseded` (ADR 0182, RN-635): a oferta deixou de ser a
            corrente — o agente alvo foi ativado por outro caminho, ou uma
            oferta mais nova ao mesmo alvo no projeto a substituiu
            (`handoff.superseded`). Só `offered` pode ser aceita.
        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: >-
            Só quando `status=error`, com a mensagem da falha. Uma resposta
            saudável não tem detalhe nenhum, de propósito: liveness não é lugar
            de expor topologia.
      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>` no 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: >-
            O publisher está no allowlist curado de fabricantes conhecidos
            (`domain/llm/huggingface-official-publishers.ts`). `false` é
            qualquer reupload de terceiro — a tela mostra o aviso de segurança
            quando `includeCommunity=true` trouxe este resultado.
      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: >-
            Combinado (RAG_SEARCH_WEIGHT_VECTOR*vetorial +
            RAG_SEARCH_WEIGHT_LEXICAL*léxico), já filtrado pelo limiar — todo
            acerto devolvido passou por ele.
        vectorScore:
          type: number
          nullable: true
          description: >-
            Similaridade de cosseno, 0..1. `null` quando este chunk não tinha
            vetor.
        lexicalScore:
          type: number
          nullable: true
          description: >-
            `ts_rank` normalizado, 0..1. `null` quando o termo não casou neste
            chunk.
        origin:
          type: object
          additionalProperties: true
          description: >-
            União discriminada por `kind`: "file" (`sourcePath`/`headingPath`)
            ou "session" (`sessionId`/`eventId`). Ver
            `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: Entre 2 e 500 caracteres — mesma régua da busca da aba Código.
        scopes:
          type: array
          description: Quando ausente, busca nos quatro escopos (RN-219/454).
          items:
            type: string
            enum:
              - docs
              - adr
              - session
              - local
        limit:
          type: number
          example: 10
          description: De 1 a 10 — quando ausente, usa o teto.
      required:
        - query
    HybridSearchResponseDto:
      type: object
      properties:
        query:
          type: string
        searchId:
          type: string
          nullable: true
          format: uuid
          description: >-
            A linha de `rag_searches` que esta busca deixou (RN-479) — o id ao
            qual um voto se prende. `null` quer dizer que a linha de telemetria
            NÃO foi gravada (o insert falhou), o que não é o mesmo que "sem
            resultados": não há a que prender um voto, e a interface precisa
            distinguir os dois para não oferecer um controle que a api vai
            recusar.
        hits:
          type: array
          items:
            $ref: '#/components/schemas/HybridSearchHitResponseDto'
        vectorAvailable:
          type: boolean
          description: >-
            `false` quando o provider de embedding não respondeu — a busca rodou
            só com o sinal léxico (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: Autorrelatada pelo modelo, não medida.
        evidenceEventIds:
          example:
            - 01JC4Z8QK3M7YV2N5T9B0PXHRB
          description: >-
            Pelo menos uma, e cada uma tem de EXISTIR nesta sessão. Evidência
            inventada é recusada com 400 — é isso que separa uma hipótese de uma
            opinião.
          type: array
          items:
            type: string
        terminationAnalysis:
          description: OBRIGATÓRIO quando `cause` indica um encerramento anormal.
          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: A rodada de análise que gerou esta hipótese.
        agenteAlvo:
          type: string
          example: dev-api
          description: Sobre qual agente é a hipótese.
        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: >-
            Confiança declarada pelo próprio modelo. Não é medição, é
            autorrelato.
        evidenceEventIds:
          example:
            - 01JC4Z8QK3M7YV2N5T9B0PXHRB
          description: >-
            Eventos do log que sustentam a hipótese. É isso que torna a
            evidência auditável em vez de aceita por fé.
          type: array
          items:
            type: string
        terminationAnalysis:
          nullable: true
          description: >-
            Presente só em hipóteses sobre uma sessão encerrada de forma
            anormal.
          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: >-
            A ORIGEM da falha. Nunca deduzida por eliminação — lição do 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: Referência OCI com tag ou digest explícito.
        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 da ação `open_infra_pr` (uma `proposed_action`) que abriu a PR. É
            o que o engine conhece de volta — não há id de artefato separado.
        gateStatus:
          type: string
          enum:
            - awaiting_qa
            - awaiting_secops
            - awaiting_user
          example: awaiting_qa
          description: Passa pelos MESMOS gates de QA e SecOps das PRs de 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 quando o projeto ainda não provisionou repositório. O subagente
            Workflows (Fase 8c) decide por aqui o formato do pipeline de CI:
            "gitlab" gera .gitlab-ci.yml, qualquer outro valor gera GitHub
            Actions.
        moduleRouting:
          description: >-
            As candidatas roteadas pelo Arquiteto (route_modules_to_infra, ADR
            0131) — entre as quais o Infra Lead elege ao propor container_start
            (ADR 0130/0133). "sem_roteamento" quando o Arquiteto ainda não
            roteou nada.
          allOf:
            - $ref: '#/components/schemas/EstadoDoRoteamentoResponseDto'
        projectImage:
          nullable: true
          description: >-
            RN-852: a decisão VIGENTE de artifact.project_image (imagem, rede,
            recursos) — o FROM do Dockerfile e a rede que o compose da Infra
            respeita. null quando nenhuma imagem foi decidida.
          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: >-
            O conteúdo vem do próprio payload da `proposed_action`: artefato de
            infra NUNCA toca worktree, como os ADRs.
          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: A `proposed_action` de patch que produziu esta versão, se houve uma.
        sourceHypothesisId:
          type: object
          example: 01JC4Z0000HIPOTESE000000001
          nullable: true
          description: A hipótese do Psicólogo que motivou o patch — o laço fechado.
        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: Se esta é a versão em vigor.
        diff:
          description: >-
            Diff desta versão contra a anterior, calculado no servidor para a
            interface não precisar de um differ próprio. A mais antiga é
            comparada com o vazio, então aparece como adição inteira.
          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: Nome pra você reconhecer este token depois — não é único.
        expiresInDays:
          type: number
          example: 90
          description: >-
            Expira em N dias. Omitido = sem expiração (revogável a qualquer
            hora, mas nunca expira sozinho).
      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: Nulo = sem expiração.
        revokedAt:
          type: object
          example: null
          nullable: true
        lastUsedAt:
          type: object
          example: null
          nullable: true
          description: Nulo = nunca usado.
        token:
          type: string
          example: brb_9f8a...
          description: >-
            Guarde agora — não é recuperável depois. Use em --token ou
            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: >-
            Como outro módulo usa o item: chamar uma função, bater numa rota,
            assinar um evento ou montar uma forma de dado.
        assinatura:
          type: string
          example: 'placePiece(board: Board, piece: Piece, pos: Pos): Board'
          description: Como outro módulo o usa. Até 300 caracteres.
        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: Quantas referências de origem a regra cita.
      required:
        - eventId
        - seq
        - actorId
        - titulo
        - descricao
        - origens
    JwksResponseDto:
      type: object
      properties:
        keys:
          description: Chaves públicas Ed25519 ativas. Duas durante uma rotação.
          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: O provider consegue LISTAR o próprio catálogo (sync do catálogo).
        embeddings:
          type: boolean
          example: false
          description: Texto → vetor (ADR 0075). Só `true` quando provado.
        routingPreference:
          type: boolean
          example: false
          description: >-
            O provider aceita uma PREFERÊNCIA DE ROTEAMENTO (`price`,
            `throughput`, `latency`) para escolher entre os upstreams que servem
            o mesmo modelo (ADR 0166). Só `true` depois que um smoke contra a
            API real devolveu o upstream escolhido — ler a documentação não
            conta.
        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: >-
            O provider aceita breakpoints explícitos de CACHE DE PROMPT
            (`cache_control`) no prefixo estável de uma chamada (RN-892). Só
            `true` depois que um smoke contra a API real mostrou tokens de
            entrada em cache numa chamada repetida.
      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: >-
            A falha do provider. Vem no CORPO com 200, não como erro HTTP,
            porque a contabilidade em `usage` continua valendo — o turno gastou,
            mesmo falhando.
        errorCode:
          type: object
          example: null
          nullable: true
          description: >-
            O `code` normalizado da falha do provider (ADR 0041) — `auth`,
            `rate_limit`, `insufficient_credit` (HTTP 402, RN-730),
            `model_not_found`, `context_length`, `timeout`, `connection` ou
            `upstream`. `null` sem erro, ou quando o erro não é do provider
            (orçamento, binding ausente). Decida por isto, nunca pelo texto de
            `error`.
        modelName:
          type: object
          example: llama3.2:3b
          nullable: true
          description: >-
            Nome do modelo que gerou a resposta (achado do problema 2) — `null`
            quando o turno falhou antes de resolver um modelo (sem binding, ou
            binding para modelo inexistente).
        truncated:
          type: boolean
          enum:
            - true
          description: >-
            Presente (sempre `true`) só quando o provider cortou a resposta no
            teto de saída (`finish_reason: "length"` / `stop_reason:
            "max_tokens"`, RN-737): o texto e os argumentos de `toolCalls` estão
            truncados. O engine se recusa a rodar uma tool call truncada e narra
            o corte.
        toolRouting:
          description: >-
            O passo em que o Jev escolheu a ferramenta (ADR 0179, RN-625).
            Ausente quando o roteador não foi consultado (provider que não é o
            OpenRouter, menos de duas ferramentas, chave do workspace
            desligada). O engine o narra como `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` leva `text`; `done` fecha com `usage`; `error` leva
            `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: >-
            Só no quadro `done`/`final` — nome do modelo que gerou a resposta
            (achado do problema 2). `null` quando o turno falhou antes de
            resolver um modelo.
        truncated:
          type: boolean
          enum:
            - true
          description: >-
            Só no quadro `final`, e só quando o provider cortou a resposta no
            teto de saída (RN-737).
      required:
        - type
    LlmUsageResponseDto:
      type: object
      properties:
        inputTokens:
          type: number
          example: 1820
        outputTokens:
          type: number
          example: 340
        costMicros:
          type: number
          example: 52700
          description: >-
            Custo em micro-USD: o custo REAL que o provider devolveu quando
            devolveu (o `usage.cost` do OpenRouter), senão o preço congelado do
            catálogo (ADR 0188, RN-665).
        estimated:
          type: boolean
          example: false
          description: >-
            `true` quando o provider não devolveu contagem e o custo foi
            ESTIMADO. O número vale menos, e quem o consome precisa poder dizer
            isso.
      required:
        - inputTokens
        - outputTokens
        - costMicros
        - estimated
    LocalFolderFileDto:
      type: object
      properties:
        path:
          type: string
          example: src/domain/rag/chunking.ts
          description: >-
            Caminho relativo dentro da pasta escolhida — nunca absoluto, nunca
            `..`.
        content:
          type: string
          description: Texto UTF-8 puro — nunca base64.
      required:
        - path
        - content
    LocalizacaoDoProjetoResponseDto:
      type: object
      properties:
        tipo:
          type: string
          enum:
            - gerenciada
            - montada
            - indisponivel
          example: gerenciada
          description: >-
            Qual das raízes do broker resolve esta pasta. `gerenciada` →
            `PROJECT_WORKSPACES_HOST_ROOT` (a pasta gerenciada pelo produto,
            nomeada por `workspaceDirName`); `montada` →
            `BRABO_PROJECTS_HOST_BASE` (a base única dos projetos Mounted, ADR
            0141); `indisponivel` → nenhuma raiz deste servidor a alcança, e
            `motivo` diz por quê. Três estados, não dois: projetos `runner` e
            projetos `mounted` legados criados fora da base são recusas com
            correções DIFERENTES, e juntá-los num null faria quem chama
            adivinhar qual recebeu.
        segmento:
          type: string
          example: loja
          description: >-
            A parte que a raiz do broker NÃO cobre, juntada a ela como
            `<root>/<segmento>`. Para `gerenciada` é `workspaceDirName`; para
            `montada`, o caminho RELATIVO sob a base (pode conter `/`). AUSENTE
            — não null — quando `tipo` é `indisponivel`: as duas variantes são
            uma união discriminada do lado da api e o fio diz o mesmo, então
            quem lê `segmento` sem conferir `tipo` quebra de forma ruidosa em
            vez de compor `<root>/null`.
        motivo:
          type: string
          example: o projeto está no modo "runner"…
          description: >-
            Por que nenhuma raiz alcança esta pasta. Presente só quando `tipo` é
            `indisponivel`. É mensagem feita para ser repetida a quem opera,
            então nomeia o modo ou a base.
      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: >-
            Um nome para um humano reconhecer esta MÁQUINA depois, na lista de
            chaves de dispositivo de todo projeto que ela atende. Não é único. O
            instalador sugere o hostname; nada aqui é derivado dele pela api.
        publicKeyJwk:
          type: string
          example: '{"kty":"OKP","crv":"Ed25519","x":"…"}'
          description: >-
            A JWK PÚBLICA Ed25519 (RFC 8037), serializada como JSON. O par é
            gerado na máquina e a metade privada nunca viaja — JWK trazendo `d`
            é recusada com 400 dizendo isso, nunca guardada.
      required:
        - name
        - publicKeyJwk
    MachineDeviceKeyInternalResponseDto:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: 01JC4Z0000CHAVE000000000001
          description: >-
            O id do registro. Grave-o na JWK PRIVADA como `kid` antes de salvar
            o arquivo (RN-475): é o único vínculo entre a chave em disco e a
            metade pública no servidor, e todo passo seguinte só o repassa.
        userId:
          type: string
          format: uuid
          example: 01JC4Z0000USUARIO0000000001
          description: >-
            O dono que a api RESOLVEU — o único usuário da instalação. Quem
            chama não o escolhe e não o manda; este campo está aqui para o
            instalador conferi-lo contra a conta que acabou de criar em vez de
            supor.
        name:
          type: string
          example: servidor-de-casa
        createdAt:
          type: string
          example: '2026-09-12T12:00:00.000Z'
          format: date-time
        replacedKeyIds:
          example: []
          description: >-
            Ids das chaves de MÁQUINA que esta SUBSTITUIU — revogadas na mesma
            transação. Vazio é o caso normal (instalação nova). Não vazio quer
            dizer que um agente local ainda segurando uma delas para de receber
            tickets, e o instalador deveria dizer isso em vez de deixar ser
            descoberto. Chaves de projeto (ADR 0118) nunca são tocadas.
          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: Só o agente que reivindicou a tarefa pode movê-la.
        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: Sempre `true`; falha vira erro HTTP.
        mensagemId:
          type: string
          example: 01JC4Z0000EVENTO000000000001
          description: >-
            Id do evento `chat.message` gravado para esta mensagem — o id que a
            cancela enquanto espera na fila.
        entrega:
          type: string
          enum:
            - lida
            - enfileirada
          example: enfileirada
          description: >-
            `lida`: o agente começou um turno com ela na hora. `enfileirada`: o
            agente estava no meio de um turno, então a mensagem entrou na fila
            dele e será lida, junto com as outras enfileiradas, num turno SÓ
            quando o atual terminar (RN-673). O log da sessão traz
            `chat.message_queued` / `chat.message_delivered` /
            `chat.message_cancelled`.
        posicao:
          type: number
          example: 2
          description: >-
            Posição na fila (1 = a próxima). Presente só quando `entrega` é
            `enfileirada`.
      required:
        - ok
        - mensagemId
        - entrega
    MirrorSyncResultInternalDto:
      type: object
      properties:
        ok:
          type: boolean
          example: true
          description: >-
            O desfecho REAL da cópia, relatado pelo agente local depois de
            terminar — nunca um "ok" otimista antes de a rodada acabar.
        destination:
          type: string
          example: /home/you/mirrors/store
          description: >-
            O destino que a rodada de fato resolveu (sucesso) ou tentou usar
            (falha), na máquina do USUÁRIO. CONGELADO na linha: mostrar o
            destino atual do projeto ao lado da cópia de ontem seria a tela
            afirmando sobre uma pasta que aquela rodada nunca tocou. Omitido
            quando a falha é sobre o próprio destino.
        filesCopied:
          type: number
          example: 412
          description: >-
            Arquivos regulares copiados nesta rodada. `0` é um número, não uma
            ausência — "sincronizou e não copiou nada" é um estado próprio
            (RN-088).
        filesSkipped:
          type: number
          example: 3
          description: >-
            Entradas que o git listou e não são arquivos regulares — um
            repositório aninhado (o worktree de um dev agent é um), um symlink,
            ou um arquivo que sumiu entre a listagem e a cópia.
        filesRefused:
          type: number
          example: 0
          description: >-
            Alvos que a guarda por arquivo recusou (um symlink escapando do
            destino). Contados, nunca engolidos.
        error:
          type: string
          example: >-
            o espelho não conseguiu listar o trabalho com o git: not a git
            repository
          description: >-
            A mensagem de falha NOMEADA. Guardada cortada quando muito longa,
            dizendo que foi cortada. Nunca apaga a última sincronização
            bem-sucedida — qual linha é a CORRENTE se decide comparando os dois
            carimbos.
      required:
        - ok
    MirrorSyncResultResponseDto:
      type: object
      properties:
        recorded:
          type: boolean
          example: true
        status:
          type: string
          example: synced
          enum:
            - never
            - synced
            - failed
          description: >-
            Qual dos três estados é o CORRENTE depois desta gravação. `never` é
            inalcançável aqui (toda gravação carimba um dos dois) e está listado
            porque o vocabulário é o mesmo que a tela lê.
      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 do workspace, projeto ou sessão a que o binding está preso. Nos
            escopos `agent` e `area`, que existem POR PROJETO, é composto:
            `<projectId>:<agent slug|area key>` (ADR 0064).
        modelId:
          type: string
          example: 01JC4Z0000MODELO00000000001
        routingPreference:
          type: string
          enum:
            - price
            - throughput
            - latency
          nullable: true
          example: null
          description: >-
            Como um hub escolhe o upstream para este binding (ADR 0166, RN-583).
            `null` = o hub decide sozinho. Só é definido para provider que
            declara a capability `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: Identificador no provider.
        displayName:
          type: string
          example: Claude Opus 4.8
        inputPricePerMillionMicros:
          type: number
          example: 15000000
          description: Preço por milhão de tokens de ENTRADA, em micro-USD.
        outputPricePerMillionMicros:
          type: number
          example: 75000000
          description: Preço por milhão de tokens de SAÍDA, em micro-USD.
        contextWindow:
          type: object
          example: 200000
          nullable: true
          description: Também a capability `context_length` do modelo.
        supportsToolCalling:
          type: boolean
          example: true
          description: >-
            Tool calling NATIVO. Sem isso o modelo é só chat e não pode ser
            vinculado a um agente (RN-040).
        supportsStreaming:
          type: boolean
          example: true
        supportsVision:
          type: boolean
          example: false
          description: >-
            Aceita entrada de IMAGEM. `false` quer dizer que o provider não
            declarou a modalidade — não que o modelo não consiga (ADR 0041).
        supportsReasoning:
          type: boolean
          example: true
          description: >-
            Aceita raciocínio explícito (thinking). No OpenRouter vem de
            `supported_parameters: reasoning`.
        generatesImage:
          type: boolean
          example: false
          description: >-
            PRODUZ imagem — eixo diferente de aceitá-la como entrada. Poucos
            modelos têm, e confundir os dois manda o usuário ao modelo errado.
        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: >-
            Preço digitado a partir da documentação do provider, não
            sincronizado. O sync de preço não sobrescreve linha marcada sem
            decisão explícita.
        availability:
          type: string
          enum:
            - available
            - unavailable
          example: available
          description: >-
            Realidade REMOTA observada pelo sync, eixo independente da
            curadoria. `unavailable` é modelo que sumiu do catálogo do provider
            — nunca é apagado, porque bindings e histórico de custo apontam para
            ele.
        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: Última vez que o sync viu o modelo no catálogo do provider.
        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: >-
            Curadoria do OWNER **deste workspace**: modelo desativado some do
            seletor mas fica nos custos históricos. Modelo descoberto pelo sync
            não tem linha de curadoria, e sem linha quer dizer `false`.
        uses:
          type: array
          example:
            - codigo
          description: >-
            Para que ESTE workspace usa o modelo. Opinião do operador, não
            capability do provider — nenhum catálogo publica "bom para código".
            Lista vazia quer dizer "ninguém tem opinião", não "não serve".
          items:
            type: string
            enum:
              - codigo
              - documentacao
              - analise
              - imagem
              - conversa
        freeRoutingAlias:
          type: boolean
          example: false
          description: >-
            Alias de roteamento livre do OpenRouter (id começando com `~`): o
            preço do catálogo é de vitrine e a fatura vem de qualquer upstream
            que serviu a chamada. A curadoria recusa ATIVÁ-LO (422
            `alias_de_roteamento_livre`); um ativado antes da regra continua
            ativo, marcado por esta flag, e uma vez desativado não volta.
            Derivado de provider e nome na leitura, nunca gravado.
      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: Identificador no provider.
        displayName:
          type: string
          example: Claude Opus 4.8
        inputPricePerMillionMicros:
          type: number
          example: 15000000
          description: Preço por milhão de tokens de ENTRADA, em micro-USD.
        outputPricePerMillionMicros:
          type: number
          example: 75000000
          description: Preço por milhão de tokens de SAÍDA, em micro-USD.
        contextWindow:
          type: object
          example: 200000
          nullable: true
          description: Também a capability `context_length` do modelo.
        supportsToolCalling:
          type: boolean
          example: true
          description: >-
            Tool calling NATIVO. Sem isso o modelo é só chat e não pode ser
            vinculado a um agente (RN-040).
        supportsStreaming:
          type: boolean
          example: true
        supportsVision:
          type: boolean
          example: false
          description: >-
            Aceita entrada de IMAGEM. `false` quer dizer que o provider não
            declarou a modalidade — não que o modelo não consiga (ADR 0041).
        supportsReasoning:
          type: boolean
          example: true
          description: >-
            Aceita raciocínio explícito (thinking). No OpenRouter vem de
            `supported_parameters: reasoning`.
        generatesImage:
          type: boolean
          example: false
          description: >-
            PRODUZ imagem — eixo diferente de aceitá-la como entrada. Poucos
            modelos têm, e confundir os dois manda o usuário ao modelo errado.
        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: >-
            Preço digitado a partir da documentação do provider, não
            sincronizado. O sync de preço não sobrescreve linha marcada sem
            decisão explícita.
        availability:
          type: string
          enum:
            - available
            - unavailable
          example: available
          description: >-
            Realidade REMOTA observada pelo sync, eixo independente da
            curadoria. `unavailable` é modelo que sumiu do catálogo do provider
            — nunca é apagado, porque bindings e histórico de custo apontam para
            ele.
        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: Última vez que o sync viu o modelo no catálogo do provider.
        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: >-
            Curadoria do OWNER **deste workspace**: modelo desativado some do
            seletor mas fica nos custos históricos. Modelo descoberto pelo sync
            não tem linha de curadoria, e sem linha quer dizer `false`.
        uses:
          type: array
          example:
            - codigo
          description: >-
            Para que ESTE workspace usa o modelo. Opinião do operador, não
            capability do provider — nenhum catálogo publica "bom para código".
            Lista vazia quer dizer "ninguém tem opinião", não "não serve".
          items:
            type: string
            enum:
              - codigo
              - documentacao
              - analise
              - imagem
              - conversa
        freeRoutingAlias:
          type: boolean
          example: false
          description: >-
            Alias de roteamento livre do OpenRouter (id começando com `~`): o
            preço do catálogo é de vitrine e a fatura vem de qualquer upstream
            que serviu a chamada. A curadoria recusa ATIVÁ-LO (422
            `alias_de_roteamento_livre`); um ativado antes da regra continua
            ativo, marcado por esta flag, e uma vez desativado não volta.
            Derivado de provider e nome na leitura, nunca gravado.
        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: Micro-USD por 1M, antes.
        inputAfterMicros:
          type: number
          example: 3000000
          description: Micro-USD por 1M, depois.
        outputBeforeMicros:
          type: number
          example: 10000000
        outputAfterMicros:
          type: number
          example: 12000000
        source:
          type: string
          enum:
            - manual
            - sync
          example: manual
          description: >-
            `manual` foi alguém digitando da documentação do provider; `sync`
            veio do catálogo remoto.
        changedBy:
          type: object
          example: 01JC4Z0000USUARIO0000000001
          nullable: true
          description: '`null` quando veio do sync — não há pessoa por trás.'
        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: >-
            Só presente quando o Hub publicou `usedStorage` na busca — nunca
            estimado por palpite.
        status:
          type: string
          enum:
            - pending_confirmation
            - confirmed
            - pulling
            - active
            - failed
          example: pending_confirmation
          description: >-
            `pending_confirmation` → `confirmed` → `pulling` → `active` |
            `failed`. Os dois primeiros estados SÃO a segunda confirmação
            explícita que o produto exige antes de qualquer download rodar.
        confirmedAt:
          type: object
          example: null
          nullable: true
          format: date-time
          description: Quando o clique de confirmação aconteceu.
        failedReason:
          type: object
          example: null
          nullable: true
          description: >-
            Só em `failed`, prefixado pela origem (infra | modelo | código |
            política) — nunca falha calada.
        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: Identificador no provider.
        displayName:
          type: string
          example: Claude Opus 4.8
        inputPricePerMillionMicros:
          type: number
          example: 15000000
          description: Preço por milhão de tokens de ENTRADA, em micro-USD.
        outputPricePerMillionMicros:
          type: number
          example: 75000000
          description: Preço por milhão de tokens de SAÍDA, em micro-USD.
        contextWindow:
          type: object
          example: 200000
          nullable: true
          description: Também a capability `context_length` do modelo.
        supportsToolCalling:
          type: boolean
          example: true
          description: >-
            Tool calling NATIVO. Sem isso o modelo é só chat e não pode ser
            vinculado a um agente (RN-040).
        supportsStreaming:
          type: boolean
          example: true
        supportsVision:
          type: boolean
          example: false
          description: >-
            Aceita entrada de IMAGEM. `false` quer dizer que o provider não
            declarou a modalidade — não que o modelo não consiga (ADR 0041).
        supportsReasoning:
          type: boolean
          example: true
          description: >-
            Aceita raciocínio explícito (thinking). No OpenRouter vem de
            `supported_parameters: reasoning`.
        generatesImage:
          type: boolean
          example: false
          description: >-
            PRODUZ imagem — eixo diferente de aceitá-la como entrada. Poucos
            modelos têm, e confundir os dois manda o usuário ao modelo errado.
        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: >-
            Preço digitado a partir da documentação do provider, não
            sincronizado. O sync de preço não sobrescreve linha marcada sem
            decisão explícita.
        availability:
          type: string
          enum:
            - available
            - unavailable
          example: available
          description: >-
            Realidade REMOTA observada pelo sync, eixo independente da
            curadoria. `unavailable` é modelo que sumiu do catálogo do provider
            — nunca é apagado, porque bindings e histórico de custo apontam para
            ele.
        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: Última vez que o sync viu o modelo no catálogo do provider.
        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: >-
            O histórico é imutável: cada mapa novo é uma versão a mais, e a
            vigente é a de maior `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: >-
            Nomes de outros módulos. Um ciclo aqui faz o mapa ser RECUSADO na
            gravação.
          type: array
          items:
            type: string
        resources:
          description: >-
            Do que ESTE módulo precisa sozinho dentro do container do projeto
            (RN-683, ADR 0199). Ausente quando o Arquiteto não declarou. A Infra
            sobe o container com a SOMA dos módulos, com piso no padrão enquanto
            houver módulo sem declaração.
          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: >-
            O ator do relatório — sempre quem chamou. Linha de outro ator não
            entra.
        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: O estado do próprio daemon, sem tradução nem colapso.
        imagem:
          type: string
          example: node:22-bookworm-slim
        iniciadoEm:
          type: object
          nullable: true
          format: date-time
          description: '`null` quando o container nunca subiu.'
      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 do agente que passou o bastão.
        toAgent:
          type: string
          example: po
          description: Slug do agente que recebe.
        artifactId:
          type: object
          example: 01JC4Z0000ARTEFATO000000001
          nullable: true
          description: Artefato que motivou o handoff (product_brief, module_map…).
        status:
          type: string
          enum:
            - offered
            - accepted
            - completed
            - rejected
            - superseded
          example: offered
          description: >-
            Este campo é MUTÁVEL — é o estado corrente. Cada transição também
            vira um evento `handoff.*` imutável no log, que é onde o histórico
            mora. `superseded` (ADR 0182, RN-635): a oferta deixou de ser a
            corrente — o agente alvo foi ativado por outro caminho, ou uma
            oferta mais nova ao mesmo alvo no projeto a substituiu
            (`handoff.superseded`). Só `offered` pode ser aceita.
        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`: uma oferta nova. `substituiu_oferta`: uma oferta nova, e
            a(s) pendente(s) ao mesmo alvo viraram `superseded`. `ja_oferecido`:
            nenhuma linha nova — a oferta pendente que já existia é devolvida
            (mesma sessão e nenhum artefato novo, ou `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 do agente que passou o bastão.
        toAgent:
          type: string
          example: po
          description: Slug do agente que recebe.
        artifactId:
          type: object
          example: 01JC4Z0000ARTEFATO000000001
          nullable: true
          description: Artefato que motivou o handoff (product_brief, module_map…).
        status:
          type: string
          enum:
            - offered
            - accepted
            - completed
            - rejected
            - superseded
          example: offered
          description: >-
            Este campo é MUTÁVEL — é o estado corrente. Cada transição também
            vira um evento `handoff.*` imutável no log, que é onde o histórico
            mora. `superseded` (ADR 0182, RN-635): a oferta deixou de ser a
            corrente — o agente alvo foi ativado por outro caminho, ou uma
            oferta mais nova ao mesmo alvo no projeto a substituiu
            (`handoff.superseded`). Só `offered` pode ser aceita.
        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`: uma oferta nova. `substituiu_oferta`: uma oferta nova, e
            a(s) pendente(s) ao mesmo alvo viraram `superseded`. `ja_oferecido`:
            nenhuma linha nova — a oferta pendente que já existia é devolvida
            (mesma sessão e nenhum artefato novo, ou `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: Sempre `true`; falha vira erro 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: O agente que perguntou.
        criadaEm:
          type: string
          format: date-time
        status:
          type: string
          enum:
            - open
            - answered
        autorDaSessao:
          type: string
          nullable: true
          description: O usuário que criou a sessão de origem — o único que pode responder.
        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: Total que casa com o filtro; `itens` tem teto de 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: >-
            Passe como `afterSeq` na próxima página; `null` quer dizer fim da
            lista.
      required:
        - items
        - nextCursor
    PaginaDeEventosResponseDto:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/SessionEventResponseDto'
        nextCursor:
          type: object
          example: 41
          nullable: true
          description: >-
            Passe como `afterSeq` para a próxima página. `null` quer dizer fim
            do log.
      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`: o agente subiu, dentro do teto do lead.
            `aguardando_autorizacao`: o pedido virou `proposed_action` e NADA
            subiu até você decidir. `recusado`: a área tem `max_parallel`
            inválido.
        actionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
          description: A ação a decidir. Só em `aguardando_autorizacao`.
        ativosNaSessao:
          type: number
          example: 2
          description: Dev agents já de pé NA SESSÃO, somados em todos os módulos.
        maxParallel:
          type: number
          example: 2
          description: O teto que o lead usa sem perguntar.
        motivo:
          type: string
          description: Por que o pedido foi recusado. Só em `recusado`.
      required:
        - estado
    PerfilApagadoResponseDto:
      type: object
      properties:
        deleted:
          type: number
          example: 2
          description: Quantos perfis foram apagados.
        optedOut:
          type: boolean
          example: true
          description: >-
            Sempre `true`: apagar SEM gravar o opt-out seria cosmético, porque a
            próxima rodada re-derivaria tudo.
      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: Padrões aprovados automaticamente.
          type: array
          items:
            type: string
        deny:
          example:
            - Terminal(rm -rf *)
            - Terminal(git push --force*)
          description: >-
            Padrões proibidos. `deny` vence `allow` e a autonomia do agente —
            nenhuma combinação de configuração libera o que está listado aqui.
          type: array
          items:
            type: string
        ask:
          example:
            - Terminal(pnpm add *)
          description: Padrões que sempre exigem decisão humana.
          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: Nulo = sem expiração.
        revokedAt:
          type: object
          example: null
          nullable: true
        lastUsedAt:
          type: object
          example: null
          nullable: true
          description: Nulo = nunca usado.
        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: Nulo = sem expiração.
        revokedAt:
          type: object
          example: null
          nullable: true
        lastUsedAt:
          type: object
          example: null
          nullable: true
          description: Nulo = nunca usado.
      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` no primeiro estágio — não há "conversão a partir de" nada.'
      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: Média simples em ms — `null` sem sessão que tenha commit E 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: >-
            Ações de git consideradas (git_commit/pr_open/git_merge, qualquer
            status).
        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: >-
            Tem de estar no catálogo devolvido por `anamnese-context` —
            competência inventada é RECUSADA. O catálogo depende do
            `module_map`, por isso a validação é de domínio, não de 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: >-
            Cada id tem de ser de um evento de sessão DESTE projeto — citar
            evento de outro projeto é recusado.
          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: >-
            Os "porquês" por trás do nível. Sem isso o perfil seria um veredito
            sem recurso.
        evidenceEventIds:
          example:
            - 01JC4Z8QK3M7YV2N5T9B0PXHRB
          description: Eventos do log que sustentam o nível.
          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` quando quem tem o perfil não é mais membro do projeto — o
            perfil sobrevive à remoção do membro.
      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: >-
            O id do EVENTO `artifact.business_rule`. É este o valor que vai em
            `business_rule_ids` ao criar a história — não há tabela de regras.
        title:
          type: string
          example: Cart accepts at most 50 items
        description:
          type: string
          example: Above that, the addition is refused with 409.
          description: >-
            O conteúdo da regra, não só o enunciado: o requisito da história vem
            dele. É o campo que distingue esta leitura da cobertura da tela.
        coveredByStoryIds:
          example:
            - 01JC4Z0000HISTORIA000000001
          description: Histórias que já citam esta regra.
          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: >-
            Quantas regras nenhuma história cobre. É a pendência do PO, e o
            número que a ferramenta `listar_regras_de_negocio` põe na frente do
            modelo.
      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` quando o projeto ainda não tem repositório provisionado.'
        provisioningStatus:
          type: string
          enum:
            - provisioning
            - provisioned
            - provision_failed
            - awaiting_plan_decision
          nullable: true
          example: provisioned
          description: '`null` quando o bootstrap nunca começou. Derivado do cursor.'
        budget:
          nullable: true
          description: >-
            `null` quando o projeto NUNCA teve orçamento definido — distinto de
            uma linha zerada, e é isso que faz o card oferecer "Definir
            orçamento".
          type: object
          allOf:
            - $ref: '#/components/schemas/ProjectCardBudgetResponseDto'
        latestSessionId:
          type: object
          example: 01JC4Z0000SESSAO00000000001
          nullable: true
        latestSeq:
          type: number
          example: 41
          description: >-
            Último `seq` já gravado na sessão mais recente (`nextSeq - 1`); 0
            quando não há sessão. A web compara isto com o que já foi visto para
            contar os não lidos.
        lastEvent:
          nullable: true
          description: O último evento da sessão mais recente — a linha de rodapé do card.
          type: object
          allOf:
            - $ref: '#/components/schemas/SessionEventResponseDto'
        storiesAwaitingPromotion:
          type: number
          example: 2
          description: >-
            Histórias que o PO terminou e que aguardam a promoção do usuário
            (RN-048).
        pendingApprovalsCount:
          type: number
          example: 8
          description: >-
            `proposed_actions` com `status = pending` no projeto INTEIRO, todas
            as sessões — não só a mais recente. É o que a barra lateral mostra
            como contador do projeto (RN-151).
        onlineAgentCount:
          type: number
          example: 2
          description: >-
            Quantos agentes estão ONLINE agora — trabalhando ou com pendência
            aguardando decisão (RN-409). Nunca o tamanho do time: dev agent
            `idle`/`idle_tripped` não conta, agente conversacional `idle` não
            conta, QA/SecOps nunca contam (veredito único por invocação).
        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: >-
            A base de projetos montados desta instalação (`BRABO_PROJECTS_BASE`,
            ADR 0141) — o mesmo valor que `GET .../projects-base` devolve,
            repetido aqui para o cliente desenhar o caminho sem uma segunda
            chamada. `null` quer dizer que a instalação não oferece o modo
            Mounted.
        path:
          type: object
          example: /home/voce/brabo/clientes
          nullable: true
          description: >-
            A pasta de fato listada, normalizada. Igual a `base` quando o pedido
            omitiu `path`. `null` só quando não há base nenhuma: nada foi
            listado porque não há onde listar.
        entries:
          example:
            - api
            - loja
            - website
          description: >-
            Os nomes dos SUBDIRETÓRIOS diretamente sob `path`, ordenados, nunca
            recursivo. Arquivos, symlinks e entradas com ponto no início ficam
            de fora — ver `arquivos`, `simbolicos` e `truncado`.
          type: array
          items:
            type: string
        truncado:
          type: boolean
          example: false
          description: >-
            A pasta tem mais de 500 subdiretórios e só os 500 primeiros
            voltaram. A ordenação acontece ANTES do corte, então o corte é
            determinístico em vez de "o que o sistema de arquivos devolveu
            primeiro".
        arquivos:
          type: number
          example: 12
          description: >-
            Quantas entradas que não são diretório ficaram de fora. Uma pasta
            cheia de código não pode parecer vazia (RN-180).
        simbolicos:
          type: number
          example: 1
          description: >-
            Quantos symlinks ficaram de fora. O navegador nunca segue um — o
            link é relatado, nunca percorrido, então um link apontando para fora
            da base não é uma saída dela.
      required:
        - base
        - path
        - entries
        - truncado
        - arquivos
        - simbolicos
    ProjectGitRemoteResponseDto:
      type: object
      properties:
        kind:
          type: string
          example: remote
          enum:
            - local
            - remote
          description: >-
            `local` é um caminho de repositório bare no disco, sem credencial;
            `remote` é uma URL que exige autenticação.
        origin:
          type: string
          example: https://github.com/daneiel/hello-api.git
          description: >-
            O que vira `git remote add origin <this>`. **Sem credencial
            embutida**, por desenho: é este o valor gravado no `.git/config` do
            workspace, legível por quem tem acesso à pasta do projeto.
        defaultBranch:
          type: string
          example: main
        token:
          type: string
          nullable: true
          description: >-
            Só em `remote`. O token do DONO do workspace (RN-058), decifrado na
            hora da chamada. Quem o recebe o injeta por invocação e o descarta.
        username:
          type: string
          nullable: true
          example: x-access-token
          description: Usuário do par HTTP Basic; o token é a senha.
      required:
        - kind
        - origin
        - defaultBranch
    ProjectMemberComUsuarioResponseDto:
      type: object
      properties:
        userId:
          type: string
          example: 01JC4Z0000USUARIO0000000002
        role:
          type: string
          enum:
            - viewer
            - developer
            - maintainer
            - owner
          example: maintainer
          description: >-
            Papel EFETIVO: o do projeto quando existe, senão o herdado do
            workspace (RN-471) — o papel de projeto SOBREPÕE o de workspace nos
            dois sentidos, para cima ou para baixo.
        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: >-
            Hierarquia linear: owner > maintainer > developer > viewer. Cada
            papel inclui as permissões dos seguintes.
        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: >-
            O destino declarado HOJE (`projects.mirror_path`, RN-515). `null`
            quer dizer que o projeto não tem espelho — o estado NORMAL, e o que
            faz a tela esconder esta linha inteira em vez de inventar uma
            ausência.
        status:
          type: string
          example: synced
          enum:
            - never
            - synced
            - failed
          description: >-
            `never` — nenhum runner relatou rodada ainda (não há linha).
            `synced` — a última rodada copiou; `filesCopied` pode ser `0`, que é
            "olhou e não havia nada a copiar" e tem frase própria. `failed` — a
            última rodada falhou, e o erro é mais novo que o último sucesso. A
            API deriva isto dos dois carimbos para a regra ter uma fonte; a tela
            ainda recebe os campos crus para escrever a frase.
        lastSyncedAt:
          type: object
          example: '2026-09-07T12:04:00.000Z'
          nullable: true
          description: >-
            A última sincronização BEM-SUCEDIDA. Uma falha posterior nunca a
            apaga — é a coisa mais útil que esta tela tem enquanto o espelho
            está quebrado.
        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: >-
            Onde a última rodada de fato escreveu — CONGELADO. Diverge de
            `mirrorPath` depois que alguém troca o destino, porque a concessão
            viaja no join e só muda quando o runner reconecta (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: Único dentro do workspace.
        workspaceDirName:
          type: string
          example: checkout-3f2b1c8e
          description: >-
            Nome da pasta física do workspace deste projeto em
            PROJECT_WORKSPACES_ROOT (RN-109). `<slug>-<8 id chars>` (8
            caracteres do id) em projeto novo; o UUID cru em projeto criado
            antes de esta coluna existir. Congelado na criação — editar `slug`
            depois não o recalcula.
        executionMode:
          type: string
          enum:
            - container
            - mounted
            - runner
          example: container
          description: >-
            ONDE o comando executa (RN-169/RN-421 — ADR 0072/0104). `container`:
            a pasta gerenciada em PROJECT_WORKSPACES_ROOT. `mounted`: a pasta do
            usuário em `workspacePath`, dentro de `BRABO_PROJECTS_BASE` e
            alcançada pelo bind-mount da base (ADR 0141). `runner`: a pasta do
            usuário confirmada pelo runner. `mounted` e `runner` relatam a
            confirmação em `workspaceVerifiedAt`.
        workspacePath:
          type: object
          example: null
          nullable: true
          description: >-
            O caminho absoluto da pasta do usuário — preenchido em
            `mounted`/`runner`, sempre `null` no modo `container`.
        workspaceVerifiedAt:
          type: object
          example: null
          nullable: true
          description: >-
            Quando o caminho foi confirmado em disco. `null` = ainda não
            verificado. Em `runner` a confirmação vem do CLI conectando
            (RN-423); em `mounted` é carimbada quando a Infra sobe o container e
            a pasta é materializada (RN-501, ADR 0142). `container` nunca
            preenche este campo. Registra UMA confirmação, nunca um batimento.
        mirrorPath:
          type: object
          example: null
          nullable: true
          description: >-
            O destino de espelho na máquina do USUÁRIO — a pasta FORA da base
            montada para onde o agente local copia o trabalho (RN-515, ADR
            0147). `null` quer dizer que o projeto não tem espelho, e esse é o
            estado NORMAL, nunca um erro. Só `mounted`/`runner` podem ter.
            Definido por `PUT /projects/:projectId/mirror-path`; vem junto em
            toda leitura do projeto para nada precisar de endpoint dedicado para
            conhecê-lo. O valor foi validado só LEXICAMENTE — se a pasta existe
            quem sabe é o agente local, não esta API.
        language:
          type: string
          example: pt-BR
          description: >-
            O idioma do PROJETO (RN-619): o idioma de tudo o que não tem autor
            humano — artefatos compartilhados (product brief, regras de negócio,
            ADRs) e turnos que ninguém digitou (kickoff, dev agents, gates,
            mensagens de commit, corpos de PR). Código BCP-47 canônico, lista
            aberta. Projeto novo começa com o idioma de resposta efetivo de quem
            o criou (RN-618). Definido por `PATCH /projects/:projectId`
            (`maintainer`). O engine o manda ao modelo como mensagem de sistema
            efêmera no fim de toda chamada de LLM de um turno sem autor humano
            (RN-622); artefato emitido durante o turno de uma pessoa continua
            seguindo o idioma de resposta dessa pessoa.
        createdBy:
          type: string
          example: 01JC4Z0000USUARIO0000000001
        taskBudgetMicros:
          type: object
          example: 500000
          nullable: true
          description: >-
            Teto de tokens por tarefa de dev agent, em micro-USD. `null` usa o
            padrão do domínio.
        maxConsecutiveBlocked:
          type: object
          example: 3
          nullable: true
          description: >-
            Circuit breaker por dev agent (Fase 12b — RN-047): tarefas
            consecutivas terminando bloqueadas antes de parar em idle_tripped.
            `null` usa o padrão do domínio.
        storyPromotion:
          type: string
          enum:
            - manual
            - auto
          example: auto
          description: >-
            Quem promove uma história para `ready` (Fase 12c — RN-048).
            `manual`: o PO propõe e o usuário decide. `auto`: promoção
            automática na criação — o padrão de projeto novo desde a RN-659;
            projetos criados antes mantêm o valor que tinham.
        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: >-
            A única pasta do computador do operador que os containers do Brabo
            enxergam, montada por IDENTIDADE (`$X:$X`) em `api` e `engine` (ADR
            0141). `null` quer dizer que `BRABO_PROJECTS_BASE` não está
            definida: a instalação não oferece o modo Mounted, e o assistente de
            projeto não deve oferecê-lo. Nunca uma falha — ausente é estado
            normal.
        brokerConfigurado:
          type: boolean
          example: false
          description: >-
            Se esta instalação tem broker de container configurado (`BROKER_URL`
            definida, ADR 0130). Projetos `container` e `mounted` só sobem
            container pelo broker (ADR 0144), e sem ele todo dev agent fica
            bloqueado para sempre (ADR 0143). Com `false` o assistente de
            projeto não pré-seleciona Mounted, mantém Container e Mounted
            visíveis mas não selecionáveis com o motivo em texto, e
            pré-seleciona Runner (ADR 0161, RN-573). Diz que a variável existe,
            nunca que o broker responde. Configuração da instalação, como
            `projectsBase`: a mesma para todo workspace.
      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: >-
            A sessão MAIS RECENTE do projeto — a mesma que `projects-summary`
            relata em `latestSessionId`.
        events:
          description: >-
            Em ordem DECRESCENTE de `seq` — o primeiro item é o mais recente
            (RN-100). No máximo 50 por projeto, o mesmo teto que `GET
            .../events` aplica sem `limit`, e quando há mais não lidos que isso
            os devolvidos são os MAIS NOVOS. Quantos ficaram de fora sai de
            `latestSeq` menos o corte, sem outro pedido.
          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: >-
            Motivo legível. A história pode ter perdido a prontidão, ou um dos
            módulos dela pode ter saído do `module_map` entre a proposta e a
            decisão.
      required:
        - storyId
        - reason
    PromoteStoriesDto:
      type: object
      properties:
        storyIds:
          example:
            - 01JC2XK9Q7
            - 01JC2XKB4M
          description: >-
            Ids das histórias a promover para `ready`. Uma só é um lote de 1.
            Cada história é validada e promovida por si — uma que falha não
            bloqueia as outras.
          type: array
          items:
            type: string
      required:
        - storyIds
    PromoteStoriesResponseDto:
      type: object
      properties:
        promoted:
          description: Ids que foram para `ready`, com as tarefas já pegáveis.
          type: array
          items:
            type: string
        failed:
          description: Vazio quando o lote inteiro passou.
          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: >-
            Se esta é a versão corrente do template. Sempre `true` logo depois
            de um upsert que criou versão nova; pode voltar `false` quando o
            acerto de idempotência (mesmo hash) aponta para uma versão que uma
            publicação POSTERIOR já desativou.
      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: >-
            Tipo da ação. Cada tipo tem papel mínimo próprio: `git_push`,
            `git_merge` e `git_branch_protect` exigem `maintainer`, o resto
            `developer`.
        actor:
          description: Quem está propondo.
          allOf:
            - $ref: '#/components/schemas/ActorDto'
        payload:
          type: object
          additionalProperties: true
          example:
            command: pnpm test
          description: Parâmetros da ação, com forma específica do `actionType`.
      required:
        - actionType
        - actor
        - payload
    ProposedActionResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRC
          description: ULID da ação.
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        sessionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
        seq:
          type: number
          example: 7
          description: Ordem da ação dentro da sessão; usada como cursor nas listagens.
        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: O que a ação faria. `git_push` e `git_merge` exigem `maintainer`.
        payload:
          type: object
          additionalProperties: true
          example:
            command: pnpm test
          description: Parâmetros da ação, específicos do `actionType`.
        status:
          type: string
          enum:
            - pending
            - approved
            - denied
            - auto_approved
            - executed
            - failed
          example: pending
          description: >-
            Estado no pipeline. `auto_approved` é distinto de `approved` de
            propósito: o log precisa distinguir o que uma pessoa decidiu do que
            a política liberou.
        resolvedPolicy:
          type: string
          enum:
            - auto_approve
            - require_approval
            - deny
          example: require_approval
          description: >-
            O que o `permissions.json` decidiu para esta ação. `deny` SEMPRE
            vence `allow` — nem a autonomia do agente o sobrepõe.
        actor:
          description: Quem propôs.
          allOf:
            - $ref: '#/components/schemas/ActorResponseDto'
        decidedBy:
          type: object
          example: null
          nullable: true
          description: Id do usuário que aprovou ou recusou. Nunca um agente.
        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: >-
            Resultado da execução, com forma específica de cada `actionType` —
            saída e código de saída para terminal, número e URL da PR para os
            tipos de git. `null` enquanto a ação não foi executada.
        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: As duas faixas usam modelos de fato diferentes; o custo diverge.
        triggeredBy:
          type: string
          enum:
            - auto
            - manual
          example: auto
        eventCount:
          type: number
          example: 128
          description: Tamanho do log analisado.
        cause:
          type: string
          enum:
            - normal
            - timeout
            - kill
            - crash
            - node_shutdown
            - unknown
          example: timeout
          description: >-
            Causa CLASSIFICADA pelo engine. É ela, não o status terminal, que
            decide se `terminationAnalysis` é obrigatório. Opcional para não
            quebrar um engine mais antigo durante um deploy gradual.
        hypotheses:
          type: array
          items:
            $ref: '#/components/schemas/HypothesisDraftDto'
      required:
        - projectId
        - tier
        - triggeredBy
        - eventCount
        - hypotheses
    ProposeHypothesesResponseDto:
      type: object
      properties:
        analysisId:
          type: string
          example: 01JC4Z0000ANALISE0000000001
          description: A rodada criada. A anterior desta sessão passa a superada.
        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: O arquivo INTEIRO como ficaria, não um diff.
        rationale:
          type: string
          example: The task was reopened three times for lack of a criterion.
        hypothesisId:
          type: string
          format: uuid
          example: 01JC4Z0000HIPOTESE000000001
          description: A hipótese que motivou o patch. É isso que fecha o laço da Anamnese.
      required:
        - projectId
        - agent
        - proposedContent
        - rationale
    ProposeMaxParallelInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        area:
          type: string
          example: dev
          description: A área cujo teto subiria.
        proposto:
          type: number
          example: 4
          minimum: 1
          description: >-
            O teto proposto. Tem de ser MAIOR que o atual — propor o mesmo ou
            menos vira ruído numa fila que o usuário precisa ler, e a Anamnese
            roda periodicamente.
        rationale:
          type: string
          example: >-
            You authorized the same request four times in this window, and none
            was denied.
          description: Ancorado em DECISÕES OBSERVADAS, não numa impressão.
      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: O id do repositório no provider.
        url:
          type: string
          example: https://github.com/acme/checkout
        defaultBranch:
          type: string
          example: main
          description: >-
            A política do projeto usa `dev`, `qa` e `main` como branches
            permanentes.
        visibility:
          type: string
          enum:
            - public
            - private
          example: private
        origin:
          type: string
          enum:
            - created
            - adopted
          example: created
          description: >-
            `created` = o Brabo criou o repositório; `adopted` = apontou para um
            que já existia. Imutável depois de gravado (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: Nome do repositório no provider.
        visibility:
          type: string
          enum:
            - public
            - private
          example: private
        namespace:
          type: string
          example: acme
          description: >-
            Organização ou grupo. Quando omitido, o repositório é criado na
            conta pessoal de quem registrou a credencial.
      required:
        - name
        - visibility
    ProvisionRepositoryResponseDto:
      type: object
      properties:
        repository:
          $ref: '#/components/schemas/ProvisionedRepositoryResponseDto'
        bootstrap:
          description: >-
            Onde o bootstrap do Gitflow parou. O trabalho continua em segundo
            plano — acompanhe por `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: >-
            Faixa de triagem. As duas usam modelos DE FATO diferentes — é isso
            que faz o custo divergir de verdade em vez de só o rótulo.
        triggeredBy:
          type: string
          enum:
            - auto
            - manual
          example: auto
          description: '`manual` é a reanálise pedida por um humano.'
        supersedes:
          type: object
          example: null
          nullable: true
          description: Id da análise que esta substitui.
        superseded:
          type: boolean
          example: false
        supersededAt:
          type: object
          example: null
          format: date-time
          nullable: true
        eventCountAtAnalysis:
          type: number
          example: 128
          description: Tamanho do log no momento da análise — o retrato que ela viu.
        createdAt:
          type: string
          example: '2026-07-26T18:00:00.000Z'
          format: date-time
        costMicros:
          type: number
          example: 4200
          description: >-
            Custo real em micro-USD, somado do `token_usage` da rodada. Só há
            linha aqui para rodada CONCLUÍDA: rodada que falhou não grava, de
            propósito.
        hypothesisCount:
          type: number
          example: 3
          description: Quantas hipóteses a rodada rendeu.
      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: >-
            Já existe uma análise VIGENTE para esta sessão. É isso que torna o
            caminho automático idempotente: com `true` o worker encerra cedo sem
            gastar nada.
        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: O que já foi proposto antes, para a rodada não se repetir.
          type: array
          items:
            $ref: '#/components/schemas/PriorHypothesisResponseDto'
      required:
        - alreadyAnalyzed
        - sessionStatus
        - terminationReason
        - businessRules
        - priorHypotheses
    PsychologistStatusResponseDto:
      type: object
      properties:
        enabled:
          type: boolean
          example: false
          description: >-
            Se uma rodada de análise NOVA pode rodar hoje, automática ou sob
            demanda. Desligado não toca dado existente — análises e hipóteses
            passadas ficam intactas e visíveis.
      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 do agente — quem vota.
      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: A posição (começando em 1) que o chunk julgado ocupou NAQUELA busca.
      required:
        - searchId
        - chunkId
        - verdict
        - rank
    RagFeedbackRequestDto:
      type: object
      properties:
        searchId:
          type: string
          format: uuid
          description: >-
            O `searchId` devolvido pela busca que produziu este acerto — nunca
            inventado pelo cliente.
        chunkId:
          type: string
          format: uuid
          description: >-
            O chunk sendo julgado. Tem de ser um dos acertos que AQUELA busca
            devolveu, senão 400.
        verdict:
          type: string
          enum:
            - util
            - irrelevante
          description: >-
            Dois valores, não uma escala de 1 a 5: uma escala mais fina convida
            diferenças de régua entre votantes que nenhuma agregação recupera
            depois.
      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: >-
            A posição (começando em 1) que o chunk julgado ocupou NAQUELA busca
            — o número que separa "o índice é pobre" de "os pesos estão
            errados".
      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)` real deste escopo — nunca um "há N minutos"
            chutado (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: >-
            A sessão em que o agente está rodando. Ausente quer dizer que quem
            chamou não tinha sessão — a linha de telemetria é gravada assim
            mesmo (RN-479), só a narração `rag.search` na timeline é pulada.
            Nunca inventada pela api: sessão que ela não recebeu é sessão que
            não existiu.
        agent:
          type: string
          example: dev-lead
          description: >-
            Slug do agente, gravado como ator da telemetria (`actor_kind:
            agent`). Ausente cai no ator `system` em vez de inventar um usuário.
      required:
        - projectId
        - query
    RagSearchInternalHitResponseDto:
      type: object
      properties:
        chunkId:
          type: string
          format: uuid
          description: >-
            O id do chunk — metade da referência de que um voto `rag_feedback`
            precisa (RN-480). Sem ele o agente poderia ler um acerto e não ter
            como dizer que foi útil.
        path:
          type: string
          example: docs/adr/0080-chat-rag-pipeline-indexacao.md
          description: >-
            Caminho do arquivo (escopos `docs`/`adr`) ou `session:<id>` (escopo
            `session`, sem caminho de arquivo real).
        chunk:
          type: string
          description: O conteúdo completo do chunk recuperado.
        score:
          type: number
          description: Pontuação combinada (vetorial + léxica), já filtrada pelo limiar.
        excerpt:
          type: string
          description: >-
            Prévia curta do chunk, para exibir sem estourar o contexto do
            modelo.
      required:
        - chunkId
        - path
        - chunk
        - score
        - excerpt
    RagSearchInternalResponseDto:
      type: object
      properties:
        searchId:
          type: string
          nullable: true
          format: uuid
          description: >-
            A linha de `rag_searches` que esta busca deixou (RN-479) — a outra
            metade da referência do voto. `null` quando a linha de telemetria
            não foi gravada; a ferramenta então omite os ids em vez de oferecer
            ao modelo uma referência que seria recusada.
        hits:
          type: array
          items:
            $ref: '#/components/schemas/RagSearchInternalHitResponseDto'
        degraded:
          type: boolean
          description: >-
            `true` quando o embedding da CONSULTA não estava disponível e a
            busca caiu para só léxica (mesma semântica de `vectorAvailable:
            false` do `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: >-
            Como `protect_branches` é o ÚLTIMO passo, reconhecer a falha dele
            fecha o bootstrap — o projeto passa a ser alcançável pelo painel.
      required:
        - status
    RecordDelegationInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        area:
          type: string
          example: qa
          description: >-
            A área que delegou — "qa" (Fase 8b) ou "infra" (Fase 8c). Texto, não
            enum.
        taskId:
          type: string
          format: uuid
          example: 01JC4Z0000TASK00000000001
          description: >-
            A tarefa de backlog por trás da delegação, quando existe. QA (Fase
            8b) sempre a manda; Infra (Fase 8c) nunca — a delegação é sobre a
            SESSÃO, não uma tarefa.
        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 do session_event `artifact.qa_verdict` do VEREDITO desta
            subespecialidade — obrigatório quando `status` é "completed".
        failureOrigin:
          type: string
          enum:
            - infra
            - modelo
            - codigo
            - politica
          example: infra
          description: >-
            A ORIGEM da falha (ADR 0020/0038), nunca por eliminação —
            obrigatória quando `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: >-
            Por que o lead escolheu NÃO delegar — obrigatório quando `status` é
            "dispensed". Nunca em branco: dispensar é uma decisão registrada,
            não silêncio (CLAUDE.md, Fase 8b item 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` (só QA, RN-860): o ciclo espera a resposta do
            usuário a uma pergunta aberta — não conta como correção.
        resumo:
          type: string
          example: Missing tests for the insufficient-stock path.
        itens:
          example:
            - No test for a nonexistent SKU
          description: O que precisa mudar, item a item.
          type: array
          items:
            type: string
        pergunta:
          type: string
          example: Should a nonexistent code answer 404?
          description: >-
            Com `needs_user_decision`: a pergunta aberta nova (até 500
            caracteres).
        sugestao:
          type: string
          example: 404 with a plain message.
          description: 'Com `needs_user_decision`: a sugestão do agente.'
        openQuestionId:
          type: string
          format: uuid
          description: >-
            Com `needs_user_decision`: uma pergunta aberta EXISTENTE em vez de
            uma nova.
        maxCorrections:
          type: number
          example: 3
          description: >-
            Teto de idas e voltas antes de a tarefa virar `blocked`. Omitido usa
            o padrão do domínio.
      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: A ação `open_infra_pr` (uma `proposed_action`) que abriu a 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: Os perfis gravados nesta rodada.
      required:
        - runId
        - profiles
    RecursosDoContainerResponseDto:
      type: object
      properties:
        cpus:
          type: number
          example: 2
          description: CPUs, valor fracionário permitido.
        memoryMb:
          type: number
          example: 4096
          description: Memória em MiB.
        pidsLimit:
          type: number
          example: 512
          description: >-
            Teto de processos. É o que contém uma fork bomb sem depender de
            allowlist de verbos.
      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: O e-mail da conta.
        senha:
          type: string
          example: correct horse battery staple
          minLength: 12
          description: >-
            Senha. Mínimo de 12 caracteres; sem exigência de maiúscula, dígito
            ou símbolo.
        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. Cifrado por envelope encryption antes de
            tocar o banco e nunca devolvido, nem cifrado. O registro NÃO testa o
            token (ADR 0050) — para verificá-lo use `POST
            /users/me/credentials/{provider}/test`. O teto de 512 caracteres é
            proteção contra payload absurdo, não validação de formato.
      required:
        - provider
        - token
    RegistroDeContainerResponseDto:
      type: object
      properties:
        status:
          type: string
          enum:
            - provisioning
            - running
            - stopped
            - failed
            - removed
          example: running
          description: O que foi REGISTRADO (project_containers.status).
        imageVersion:
          type: number
          example: 1
        imagem:
          type: object
          nullable: true
          example: node:22-bookworm-slim
          description: >-
            A imagem CONGELADA em `imageVersion`, resolvida do evento
            `artifact.project_image` naquela versão exata — nunca a vigente, que
            pode ter sido revisada desde então. `null` quando o evento daquela
            versão não foi encontrado.
        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: >-
            O nome novo. `null` (ou texto em branco) LIMPA o nome e a sessão
            volta a se identificar só pela hashtag — é o caminho de desfazer,
            por isso o campo aceita null em vez de exigir uma rota de remoção.
      required:
        - name
    RepoBootstrapStatusResponseDto:
      type: object
      properties:
        status:
          type: string
          enum:
            - provisioning
            - provisioned
            - provision_failed
            - awaiting_plan_decision
          example: provisioning
          nullable: true
          description: >-
            `null` quando nunca houve provisionamento neste projeto.
            `awaiting_plan_decision` é um repositório ADOTADO com plano gerado
            ainda não decidido: nada roda até alguém aprovar o plano ou adotá-lo
            como está.
        sessionId:
          type: object
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
          nullable: true
          description: >-
            A sessão DEDICADA do bootstrap. A tela de progresso lê os eventos
            `bootstrap.step_*` dela para montar o checklist ao vivo.
        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: >-
            Quantas vezes o bootstrap já foi tentado. É idempotente e retomável:
            repetir não duplica o que já funcionou.
      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` NÃO é terminal: quer dizer "estou liberando esta sessão".
            Se outra réplica a adota, ela continua viva; se ninguém a adota
            antes do timeout, o drain manda `closed_abnormally`. `created` e
            `active` ficam de fora porque entrar neles é decisão da api, nunca
            do engine relatando um encerramento.
        reason:
          type: string
          example: heartbeat_timeout
          description: A ORIGEM do encerramento, não um diagnóstico por eliminação.
      required:
        - projectId
        - to
    RequestManualHandoffDto:
      type: object
      properties:
        toAgent:
          type: string
          example: staff
          description: >-
            Slug do agente alvo. Tem de ser um lead ou um agente sem área —
            nunca um subagente.
      required:
        - toAgent
    RequestModelPullDto:
      type: object
      properties:
        repoId:
          type: string
          example: meta-llama/Llama-3.1-8B-Instruct-GGUF
          description: '`<publisher>/<modelo>` no Hugging Face Hub.'
        estimatedSizeBytes:
          type: number
          example: 4900000000
          description: >-
            Vem da busca quando o Hub publicou o tamanho — nunca estimado por
            palpite no cliente.
      required:
        - repoId
    RequestPasswordResetDto:
      type: object
      properties:
        email:
          type: string
          example: fulano@brabo.dev
      required:
        - email
    ResetPasswordDto:
      type: object
      properties:
        token:
          type: string
          description: Token de uso único recebido por e-mail.
        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: >-
            De qual escopo veio o valor. A cascata é sessão → agente → projeto →
            workspace, e expor a ORIGEM é o que deixa a interface dizer "herdado
            do projeto" em vez de mostrar um valor sem procedência.
        routingPreference:
          type: string
          enum:
            - price
            - throughput
            - latency
          nullable: true
          example: null
          description: >-
            A preferência de roteamento DO BINDING QUE VENCEU a cascata (ADR
            0166). Ela nunca cascateia sozinha: nível pulado leva a preferência
            junto, e nenhum nível herda só a preferência de outro.
        skipped:
          description: >-
            Escopos mais específicos que a cascata descartou antes de chegar em
            `origin`. Vazio no caminho normal.
          type: array
          items:
            $ref: '#/components/schemas/SkippedBindingResponseDto'
        herdadoDoLead:
          type: string
          example: secops
          description: >-
            Presente quando o agente perguntado não tinha modelo próprio (nem da
            área nem do projeto) e herdou o binding RESOLVIDO do lead da área
            dele (RN-703). `origin` é então a origem do binding do 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: >-
            Modelos que não existiam no banco. Entram INATIVOS — ativar é
            curadoria do owner (RN-043).
        reencontrados:
          type: number
          example: 1
          description: >-
            Modelos que estavam `unavailable` e voltaram ao catálogo. O
            `isActive` deles fica intocado: a escolha do owner sobrevive à
            ausência.
        indisponibilizados:
          type: number
          example: 0
          description: >-
            Modelos que sumiram do catálogo remoto. Viram `unavailable` e NUNCA
            são apagados — bindings e histórico de custo apontam para eles.
        pulado:
          type: string
          enum:
            - sem_capability
            - sem_credencial
            - falha
          description: >-
            Presente quando o provider não foi sincronizado. Provider pulado não
            marca nada como indisponível: "não sei o que há lá" não é "não há
            nada lá".
        origemDaFalha:
          type: string
          enum:
            - infra
            - modelo
          description: >-
            Só com `pulado: "falha"`. Vocabulário de origem do ADR 0020 —
            `infra` quando o provider nem foi alcançado, `modelo` quando
            respondeu recusando.
        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: >-
            O motivo é OBRIGATÓRIO: vira uma mensagem fixada na sessão do PO, e
            uma devolução sem motivo devolve trabalho sem informação.
      required:
        - reason
    RollbackResponseDto:
      type: object
      properties:
        agent:
          type: string
          example: dev-api
        restoredFrom:
          type: number
          example: 2
          description: A versão cujo conteúdo foi restaurado.
        toVersion:
          type: number
          example: 5
          description: >-
            A versão NOVA que o rollback criou. O histórico é imutável:
            restaurar não apaga nada, acrescenta.
        cacheInvalidated:
          type: boolean
          example: true
          description: Se o cache de instruções do engine foi invalidado com sucesso.
      required:
        - agent
        - restoredFrom
        - toVersion
        - cacheInvalidated
    RosterFactsResponseDto:
      type: object
      properties:
        executionActivated:
          type: boolean
          example: true
          description: >-
            A sessão mais recente já gravou `execution.activated` — é isso que
            traz os dev agents por módulo para a lista.
        moduleNames:
          example:
            - api
            - web
          description: >-
            Nomes de módulo do module_map VIGENTE (maior `version`). Um dev
            agent por módulo, depois que a execução foi ativada.
          type: array
          items:
            type: string
        gatesEverOpened:
          type: boolean
          example: true
          description: >-
            Algum gate de PR (dev ou infra) abriu AO MENOS UMA VEZ nesta sessão
            — é isso que traz QA e SecOps para a lista. Cobre a sessão inteira,
            não uma janela dos últimos N eventos.
        delegatedSubagents:
          example:
            - qa-automacao
          description: >-
            Subagentes com ao menos uma delegação registrada na sessão, qualquer
            que seja o desfecho — dispensa é decisão registrada, não silêncio.
          type: array
          items:
            type: string
        activatedAgents:
          example:
            - arquiteto
            - infra
          description: >-
            Agentes com ao menos um `agent.activated` na sessão, o ativado mais
            recentemente primeiro (por `seq`), uma entrada por agente (RN-630).
            Cobre a sessão INTEIRA, não a janela de 200 eventos: o cliente o
            soma à janela e só confia nele quando `latestSessionId` é a sessão
            que está lendo.
          type: array
          items:
            type: string
        infraActive:
          type: boolean
          example: false
          description: Existe um handoff `accepted` para `infra` na sessão mais recente.
        uxDesignerActive:
          type: boolean
          example: false
          description: >-
            Existe um handoff `accepted` para `ux-designer` na sessão mais
            recente (ADR 0087).
        staffActive:
          type: boolean
          example: false
          description: >-
            Existe um handoff `accepted` para `staff` na sessão mais recente
            (docs/fluxo.yml, ADR 0088) — dormente para disparo automático, só
            reflete uma ativação MANUAL já aceita.
      required:
        - executionActivated
        - moduleNames
        - gatesEverOpened
        - delegatedSubagents
        - activatedAgents
        - infraActive
        - uxDesignerActive
        - staffActive
    RoteamentoDeModuloInternalDto:
      type: object
      properties:
        modulo:
          type: string
          example: checkout-api
          description: Nome do módulo — tem de existir no module_map vigente.
        imagemCandidata:
          type: string
          example: node:22-bookworm-slim
          description: >-
            Referência OCI com TAG ou digest explícito para este módulo.
            `latest` é RECUSADO com 400 — a regra é de domínio.
        porque:
          type: string
          example: >-
            This module is TypeScript on Node 22; the slim variant has the
            runtime and nothing else.
          description: Por que ESTA imagem para ESTE módulo. Mínimo de 10 caracteres.
      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: >-
            Um item por módulo do module_map vigente. A imagem candidata é o
            Arquiteto propondo, não decidindo — a Infra elege entre as
            candidatas num passo posterior.
          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: A PR commita um `.env.example` para copiar antes de rodar.
        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: Atribui o custo a este agente no painel do time.
        messages:
          type: array
          items:
            type: object
            additionalProperties: true
          example:
            - role: user
              content: Read file X.
          description: Histórico já montado pelo ContextManager do engine.
        tools:
          type: array
          items:
            type: object
            additionalProperties: true
          description: Ferramentas oferecidas ao modelo; `parameters` é JSON Schema.
        catalogoCompleto:
          type: boolean
          example: true
          description: >-
            Pede o catálogo INTEIRO de ferramentas: o roteador de ferramentas do
            Jev (ADR 0179) é pulado nesta chamada. O engine o liga quando repete
            um passo cujo menu restrito fez o modelo responder sem chamar
            ferramenta.
      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: >-
            Nulo = chave de MÁQUINA (ADR 0154): vale para qualquer projeto do
            dono dela, e não só para este. Ver `especie`.
        especie:
          type: string
          enum:
            - projeto
            - maquina
          example: projeto
          description: >-
            `projeto` = presa ao projeto desta rota (ADR 0118). `maquina` =
            descreve a MÁQUINA (ADR 0154) e aparece na listagem de todo projeto
            que ela atende — revogá-la derruba o agente local em todos eles.
        createdAt:
          type: string
          example: '2026-08-27T12:00:00.000Z'
          format: date-time
        revokedAt:
          type: object
          example: null
          nullable: true
          description: Nulo = ativa. Revogada continua aparecendo na lista.
        lastUsedAt:
          type: object
          example: null
          nullable: true
          description: >-
            Nulo = nunca usada — o sinal de uma chave órfã (aba fechada no meio
            do fluxo de configuração automática do 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: >-
            O nome da pasta do projeto (RN-109) — segmento relativo sob a base
            da máquina, nunca um caminho absoluto.
        workspaceVerifiedAt:
          type: object
          example: null
          nullable: true
          description: >-
            Quando o runner confirmou a pasta pela primeira vez (RN-423). Nulo =
            nunca confirmada. É registro de uma confirmação, não batimento
            (RN-468): não diz que a pasta está de pé agora.
      required:
        - projectId
        - name
        - workspaceDirName
        - workspaceVerifiedAt
    RunnerTicketResponseDto:
      type: object
      properties:
        ticket:
          type: string
          description: >-
            Token opaco de uso único, base64url. TTL de 30s: some depois do
            primeiro `connect/3` bem-sucedido no socket `/runner`, ou quando
            expira.
        expiresAt:
          type: string
          example: '2026-08-09T12:00:30.000Z'
          description: Quando o ticket expira (ISO 8601).
        engineWsUrl:
          type: string
          example: ws://localhost:4000/runner
          description: >-
            URL pública do socket Phoenix `/runner`, já em ws(s):// — pronta
            para o cliente (runner CLI ou web) abrir a conexão com `{ticket}`
            nos params.
      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: >-
            Título da regra RECÉM-acrescentada. Regras com o mesmo título
            normalizado (a RN-080 faz dela a nova) ficam fora da comparação.
      required:
        - projectId
        - kind
        - title
    SemanticDuplicateCheckResponseDto:
      type: object
      properties:
        status:
          type: string
          enum:
            - warned
            - clean
            - skipped
            - nothing_to_compare
          description: >-
            `warned` NUNCA bloqueia: o item já foi gravado. `skipped` traz o
            motivo — sem provider de embedding, daemon fora do ar, o teto de 10
            s — e também é narrado no event log.
        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: Limiar de cosseno — PONTO DE PARTIDA, não calibrado (ADR 0198).
        compared:
          type: number
          example: 12
          description: >-
            Quantos itens existentes foram comparados (os mais recentes, até
            100).
        total:
          type: number
          example: 12
          description: Quantos existem no projeto.
        reason:
          type: string
          example: provider "ollama" did not answer
        message:
          type: string
          nullable: true
          description: >-
            A frase que o agente lê no resultado da ferramenta; `null` quando
            não há o que dizer.
      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: Mensagem do usuário ao agente ativo da sessão.
      required:
        - text
    SendChatMessageDto:
      type: object
      properties:
        text:
          type: string
          example: Summarize what we have already decided about checkout.
          description: A mensagem do usuário. A resposta volta como stream SSE.
      required:
        - text
    SessaoResponseDto:
      type: object
      properties:
        accessToken:
          type: string
          description: JWT EdDSA de vida curta.
        expiresIn:
          type: number
          example: 900
          description: Segundos até o access token expirar.
        locale:
          type: string
          example: pt-BR
          enum:
            - pt-BR
            - en
            - ru
            - zh-Hans
          description: >-
            A preferência de idioma do usuário (fundação de i18n, Onda 6a). Vem
            aqui — e não numa chamada separada — para a web nunca precisar de
            uma ida e volta a mais só para saber em que idioma desenhar a tela.
      required:
        - accessToken
        - expiresIn
        - locale
    SessionAgentTokenUsageResponseDto:
      type: object
      properties:
        actorId:
          type: string
          example: dev-api
          description: O slug do agente, ou o id do usuário.
        costMicros:
          type: number
          example: 3400000
          description: Custo em micro-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: >-
            A sessão pedida mais a(s) sessão(ões) de execução que o plano
            aprovado do Dev Lead dela ativou (RN-828).
          type: array
          items:
            type: string
        regras:
          type: number
          example: 11
          description: '`artifact.business_rule` na sessão inteira.'
        decisoes:
          type: number
          example: 6
          description: '`artifact.decision_record` na sessão inteira.'
        itensDeRegras:
          description: >-
            As regras de negócio mais recentes da sessão, teto de 50; o total é
            `regras` (RN-844).
          type: array
          items:
            $ref: '#/components/schemas/ItemDeRegraResponseDto'
        itensDeDecisoes:
          description: >-
            Os registros de decisão mais recentes da sessão, teto de 50; o total
            é `decisoes` (RN-844).
          type: array
          items:
            $ref: '#/components/schemas/ItemDeDecisaoResponseDto'
        artefatosPorAgente:
          type: array
          items:
            $ref: '#/components/schemas/ArtefatosDoAgenteResponseDto'
        arquivosTocados:
          description: >-
            Arquivos das PRs abertas nesta sessão e na sessão de execução ligada
            (lidos do diff da PR, RN-844), depois os arquivos dos payloads de
            `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: O ULID do evento.
        sessionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
        seq:
          type: number
          example: 41
          description: Ordem dentro da sessão. Monotônica e sem lacunas.
        type:
          type: string
          example: chat.message
          description: >-
            Tipo do evento. O inventário completo está em
            `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: Forma livre, específica de cada `type`. Ver a referência de eventos.
        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: O ULID da sessão.
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        createdBy:
          type: string
          example: 01JC4Z0000USUARIO0000000001
          description: Quem abriu a sessão.
        status:
          type: string
          enum:
            - created
            - active
            - closing
            - closed
            - closed_abnormally
          example: active
          description: >-
            Máquina de estados explícita: created → active → closing → closed |
            closed_abnormally. Transição inválida responde 409.
        kind:
          type: string
          enum:
            - consultiva
            - criativa
          example: criativa
          description: >-
            A INTENÇÃO com que a sessão foi aberta, escolhida na criação e
            imutável. `consultiva` é só conversa; `criativa` produz e é a única
            que entra em execução. Não confundir com o estado de execução, que
            continua sendo o evento `execution.activated` no log.
        name:
          type: object
          example: Cart checkout
          nullable: true
          description: >-
            Nome amigável, ou `null`. As telas o compõem com a hashtag do id;
            ele nunca a substitui.
        nextSeq:
          type: number
          example: 42
          description: >-
            Próximo `seq` do event log. Serve de cursor: `?afterSeq=41` busca
            tudo depois do 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: Só preenchido em estados terminais.
        terminationReason:
          type: object
          example: null
          nullable: true
          description: >-
            Motivo relatado pelo engine ao encerrar (heartbeat_timeout,
            conversation_idle_timeout, killed, exception…). `null` num
            fechamento humano ou numa sessão ainda viva.
        traceParent:
          type: object
          example: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
          nullable: true
          description: >-
            `traceparent` W3C do span raiz. É assim que a sessão inteira é
            recuperada no Tempo.
        technical:
          type: boolean
          example: false
          description: >-
            Se esta é a sessão TÉCNICA aberta pelo provisionamento do
            repositório (a ligada por `repo_bootstraps`), em oposição a uma
            sessão de trabalho. É o vínculo, nunca o nome: renomear a sessão não
            o muda. Telas que escolhem "a sessão mais recente" a pulam, a menos
            que seja a única.
      required:
        - id
        - projectId
        - createdBy
        - status
        - kind
        - name
        - nextSeq
        - createdAt
        - updatedAt
        - closedAt
        - terminationReason
        - traceParent
        - technical
    SessionPendingWorkResponseDto:
      type: object
      properties:
        pending:
          type: boolean
          example: true
          description: >-
            Há trabalho que impede o fechamento por inatividade da aba: um
            handoff `offered`, uma ação `pending`, um agente no meio de um
            turno, um dev agent trabalhando ou bloqueado, ou um agente
            conversacional esperando o usuário (RN-064, RN-581).
        motivo:
          type: object
          example: handoff po → arquiteto aguardando aceite
          nullable: true
          description: O que está pendurado. `null` quando não há nada.
        aguardandoUsuarioDesde:
          type: string
          example: '2026-09-18T12:00:00.000Z'
          nullable: true
          description: >-
            Definido SÓ quando a única pendência é um agente conversacional
            esperando o usuário (RN-581): o instante em que o turno dele
            terminou. É o único sinal de pendência com teto, e o engine o aplica
            (`SESSION_CONVERSATION_IDLE_TIMEOUT_MS`, padrão 8h) — passado ele a
            sessão fecha com `conversation_idle_timeout`. `null` caso contrário.
      required:
        - pending
        - motivo
        - aguardandoUsuarioDesde
    SessionResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
          description: O ULID da sessão.
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        createdBy:
          type: string
          example: 01JC4Z0000USUARIO0000000001
          description: Quem abriu a sessão.
        status:
          type: string
          enum:
            - created
            - active
            - closing
            - closed
            - closed_abnormally
          example: active
          description: >-
            Máquina de estados explícita: created → active → closing → closed |
            closed_abnormally. Transição inválida responde 409.
        kind:
          type: string
          enum:
            - consultiva
            - criativa
          example: criativa
          description: >-
            A INTENÇÃO com que a sessão foi aberta, escolhida na criação e
            imutável. `consultiva` é só conversa; `criativa` produz e é a única
            que entra em execução. Não confundir com o estado de execução, que
            continua sendo o evento `execution.activated` no log.
        name:
          type: object
          example: Cart checkout
          nullable: true
          description: >-
            Nome amigável, ou `null`. As telas o compõem com a hashtag do id;
            ele nunca a substitui.
        nextSeq:
          type: number
          example: 42
          description: >-
            Próximo `seq` do event log. Serve de cursor: `?afterSeq=41` busca
            tudo depois do 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: Só preenchido em estados terminais.
        terminationReason:
          type: object
          example: null
          nullable: true
          description: >-
            Motivo relatado pelo engine ao encerrar (heartbeat_timeout,
            conversation_idle_timeout, killed, exception…). `null` num
            fechamento humano ou numa sessão ainda viva.
        traceParent:
          type: object
          example: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
          nullable: true
          description: >-
            `traceparent` W3C do span raiz. É assim que a sessão inteira é
            recuperada no 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: O idioma efetivo.
        origin:
          type: string
          enum:
            - sessao
            - conta
            - detectado
            - interface
          example: interface
          description: >-
            De onde veio `language`: `sessao` (fixado nesta sessão) > `conta`
            (escolha da Conta) > `detectado` (detectado E confirmado) >
            `interface` (o idioma da interface).
        sessionOverride:
          type: string
          nullable: true
          example: null
        account:
          type: string
          example: automatico
          description: '`automatico` ou o código escolhido na Conta.'
        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: >-
            O idioma para o qual as SUAS mensagens recentes apontam, quando a
            tela deveria PERGUNTAR se usá-lo nas respostas (RN-624) — `null`
            quer dizer sem pergunta. A detecção nunca muda a preferência
            sozinha: só responder `confirm` em `POST
            /users/me/preferences/detected-language` muda. Nunca é perguntado
            quando o idioma efetivo vem de uma escolha explícita (esta sessão ou
            a Conta), quando ele já é o detectado, ou quando você recusou este
            idioma antes. Melhor esforço: falha na detecção é `null`, nunca um
            erro desta rota.
      required:
        - language
        - origin
        - sessionOverride
        - account
        - detected
        - interfaceLocale
        - detectionQuestion
    SetAgentAutonomyDto:
      type: object
      properties:
        agentId:
          type: string
          example: dev-api
          description: Slug do agente.
        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: >-
            Tipo de ação, ou `"*"` para TODO tipo de ação deste agente — o "modo
            automático" do ApprovalCard (RN-153). Regra ESPECÍFICA sempre vence
            a curinga: gravar `terminal: deny` com `"*": auto_approve` já ligado
            continua negando terminal.
        mode:
          type: string
          enum:
            - auto_approve
            - require_approval
            - deny
          example: auto_approve
          description: >-
            A autonomia deste agente para este tipo. NÃO sobrepõe o
            `permissions.json`: padrão em `deny` continua bloqueado por mais
            autonomia que o agente tenha.
      required:
        - agentId
        - actionType
        - mode
    SetAreaBudgetDto:
      type: object
      properties:
        limitUsd:
          type: object
          example: 20
          minimum: 0
          nullable: true
          description: >-
            Teto em DÓLARES para a área — a mesma convenção dólar na entrada,
            micro-USD na saída dos orçamentos de projeto/sessão
            (`BudgetsController`). `null` LIMPA o teto: a área volta a
            ilimitada, que também é o padrão de área que nunca teve um.
      required:
        - limitUsd
    SetMaxParallelDto:
      type: object
      properties:
        maxParallel:
          type: number
          example: 4
          minimum: 1
          description: >-
            Quantos agentes o lead da área pode ter na sessão SEM pedir a sua
            autorização. Mínimo 1 — zero não quer dizer "ilimitado", quer dizer
            configuração inválida, e tratá-lo como ilimitado transformaria um
            erro de digitação em gasto irrestrito.
      required:
        - maxParallel
    SetMirrorPathDto:
      type: object
      properties:
        mirrorPath:
          type: string
          nullable: true
          example: /home/you/mirrors/store
          description: >-
            O caminho absoluto, NA MÁQUINA DO USUÁRIO, para onde o agente local
            copia o trabalho do projeto (RN-515, ADR 0147). Mande `null` — a
            chave é OBRIGATÓRIA, omiti-la é 400 — para limpar o destino e
            desligar o espelho; `null` é o estado normal de um projeto, não um
            erro. Só projetos `mounted`/`runner` podem ter: em `container` a
            origem é um volume gerenciado do lado do servidor que o agente local
            não enxerga, e o pedido é recusado com 400 nomeando esse motivo. A
            API valida SÓ a forma LÉXICA (absoluto, sem `..`/`.`, nunca a raiz,
            pasta de sistema ou sobreposto ao próprio checkout do Brabo) mais os
            dois sentidos do laço origem↔destino (o destino não pode estar
            dentro de `workspacePath`, nem contê-lo) — nunca toca o disco,
            porque não enxerga a máquina onde o destino vai morar, exatamente
            como no modo `runner` (RN-423). Resolver symlinks é a metade da
            guarda do agente local, não esta.
      required:
        - mirrorPath
    SetModelBindingDto:
      type: object
      properties:
        modelId:
          type: string
          format: uuid
          example: 9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b
          description: Modelo a fixar neste escopo. Tem de existir em `GET /models`.
        routingPreference:
          type: string
          enum:
            - price
            - throughput
            - latency
          nullable: true
          example: throughput
          description: >-
            Como um HUB escolhe o upstream que serve o modelo (ADR 0166,
            RN-583). AUSENTE mantém o valor gravado quando o provider do modelo
            novo o aceita, e o limpa caso contrário; `null` o limpa; valor para
            modelo cujo provider não declara a capability `routingPreference`
            (`GET /llm/provider-capabilities`) é recusado com 422. Viaja JUNTO
            com este binding: nunca cascateia sozinho.
      required:
        - modelId
    SetModelsActiveDto:
      type: object
      properties:
        modelIds:
          example:
            - 9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b
          description: Modelos a ligar ou desligar. Lote inteiro ou nada.
          type: array
          items:
            type: string
            format: uuid
        isActive:
          type: boolean
          example: true
          description: >-
            `true` faz o modelo aparecer no seletor e poder receber binding
            novo. Não toca `availability`, que é o que o sync observou no
            provider.
      required:
        - modelIds
        - isActive
    SetModelUsesDto:
      type: object
      properties:
        modelIds:
          example:
            - 9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b
          description: Modelos a marcar. Lote inteiro ou nada.
          type: array
          items:
            type: string
            format: uuid
        uses:
          type: array
          example:
            - codigo
            - analise
          description: >-
            The COMPLETE list of uses — replaces the previous one, does not add
            to it. An empty list unmarks all. A closed vocabulary: a use outside
            it fails the call, because free text would give `code`, `coding`,
            and `código` in the same filter.
          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: Padrões aprovados automaticamente.
          type: array
          items:
            type: string
        deny:
          example:
            - Terminal(rm -rf *)
            - Terminal(git push --force*)
          description: >-
            Padrões proibidos. `deny` vence `allow` e vence a autonomia do
            agente.
          type: array
          items:
            type: string
        ask:
          example:
            - Terminal(pnpm add *)
          description: Padrões que sempre pedem decisão humana.
          type: array
          items:
            type: string
      required:
        - allow
        - deny
        - ask
    SetSessionResponseLanguageDto:
      type: object
      properties:
        language:
          type: string
          nullable: true
          example: en
          maxLength: 35
          description: >-
            O idioma em que os agentes respondem a VOCÊ, só NESTA sessão
            (RN-618) — os outros participantes nunca são afetados. Qualquer
            código BCP-47 que o servidor reconhece, gravado na forma canônica.
            Mande `null` (ou `automatico`) — a chave é OBRIGATÓRIA — para voltar
            a herdar da sua Conta.
      required:
        - language
    SetToolRouterDto:
      type: object
      properties:
        enabled:
          type: boolean
          example: false
          description: >-
            Se o Jev escolhe a ferramenta de cada passo de agente. Ligado por
            padrão; só age quando o modelo do turno é do 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`: o modelo sumiu do catálogo do provider.
            `sem_tool_calling`: o pedido é de um agente e o modelo é só chat — a
            cascata revalida a capability em todo nível para não violar a RN-040
            em silêncio.
      required:
        - scope
        - modelId
        - reason
    SocketTicketResponseDto:
      type: object
      properties:
        ticket:
          type: string
          description: >-
            Token opaco de uso único, base64url. TTL de 30s e uso único: some
            depois do primeiro `connect/3` bem-sucedido, ou quando expira.
        expiresAt:
          type: string
          example: '2026-08-09T12:00:30.000Z'
          description: Quando o ticket expira (ISO 8601).
      required:
        - ticket
        - expiresAt
    SpendLinhaResponseDto:
      type: object
      properties:
        chave:
          type: string
          example: anthropic/claude-sonnet-4
          description: >-
            A chave de agrupamento: nome do modelo, nome do provider, id do
            projeto, id do ator ou id da sessão, conforme a lista em que a linha
            aparece.
        rotulo:
          type: object
          example: Loja
          nullable: true
          description: >-
            Nome legível, quando há tabela com nome (projeto). `null` quando a
            chave já é o rótulo.
        actorKind:
          type: object
          example: agent
          nullable: true
          description: >-
            Só as listas por ator preenchem isto (`porAtor`, `porOwner`,
            `porAgente`); nas outras é `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: 'Dia em UTC. A série é DENSA: dia sem gasto vem como zero.'
        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: Requisitos funcionais.
          type: array
          items:
            type: string
        rnf:
          example:
            - The response stays under 200 ms at p95
          description: Requisitos não funcionais.
          type: array
          items:
            type: string
        businessRuleIds:
          example:
            - RN-014
          description: >-
            Regras de negócio cobertas. É por aqui que a cobertura
            regra→história é calculada em `GET /projects/:id/coverage`.
          type: array
          items:
            type: string
        dod:
          example:
            - Unit tests green
          description: Definição de pronto.
          type: array
          items:
            type: string
        dor:
          example:
            - Module defined
          description: Definição de preparado.
          type: array
          items:
            type: string
        moduleIds:
          example:
            - api
          description: >-
            Módulos do `module_map` que a história toca. História sem módulo, ou
            com módulo inexistente, vira pendência de arquitetura.
          type: array
          items:
            type: string
        status:
          type: string
          enum:
            - draft
            - ready
            - in_progress
            - done
          example: ready
        proposedReady:
          type: boolean
          example: true
          description: >-
            O PO terminou a história e ela espera a decisão do usuário (Fase 12c
            — RN-048). Convive com `status: "draft"`: é uma proposta, não um
            estado. Sempre `false` num projeto no modo `auto`.
        returnedReason:
          type: object
          example: DoD too generic — spell out the acceptance criteria.
          nullable: true
          description: >-
            Por que o usuário devolveu a história ao PO. `null` quando nunca foi
            devolvida.
        returnedAt:
          type: object
          example: '2026-08-02T14:00:00.000Z'
          format: date-time
          nullable: true
        archivedAt:
          type: string
          example: null
          format: date-time
          description: >-
            Quando a história foi ARQUIVADA (RN-727). Histórias arquivadas saem
            do backlog, da cobertura, do plano e do claim; `null` é uma história
            em jogo.
          nullable: true
        archivedReason:
          type: string
          example: null
          description: Por que foi arquivada, quando alguém disse (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: Requisitos funcionais.
          type: array
          items:
            type: string
        rnf:
          example:
            - The response stays under 200 ms at p95
          description: Requisitos não funcionais.
          type: array
          items:
            type: string
        businessRuleIds:
          example:
            - RN-014
          description: >-
            Regras de negócio cobertas. É por aqui que a cobertura
            regra→história é calculada em `GET /projects/:id/coverage`.
          type: array
          items:
            type: string
        dod:
          example:
            - Unit tests green
          description: Definição de pronto.
          type: array
          items:
            type: string
        dor:
          example:
            - Module defined
          description: Definição de preparado.
          type: array
          items:
            type: string
        moduleIds:
          example:
            - api
          description: >-
            Módulos do `module_map` que a história toca. História sem módulo, ou
            com módulo inexistente, vira pendência de arquitetura.
          type: array
          items:
            type: string
        status:
          type: string
          enum:
            - draft
            - ready
            - in_progress
            - done
          example: ready
        proposedReady:
          type: boolean
          example: true
          description: >-
            O PO terminou a história e ela espera a decisão do usuário (Fase 12c
            — RN-048). Convive com `status: "draft"`: é uma proposta, não um
            estado. Sempre `false` num projeto no modo `auto`.
        returnedReason:
          type: object
          example: DoD too generic — spell out the acceptance criteria.
          nullable: true
          description: >-
            Por que o usuário devolveu a história ao PO. `null` quando nunca foi
            devolvida.
        returnedAt:
          type: object
          example: '2026-08-02T14:00:00.000Z'
          format: date-time
          nullable: true
        archivedAt:
          type: string
          example: null
          format: date-time
          description: >-
            Quando a história foi ARQUIVADA (RN-727). Histórias arquivadas saem
            do backlog, da cobertura, do plano e do claim; `null` é uma história
            em jogo.
          nullable: true
        archivedReason:
          type: string
          example: null
          description: Por que foi arquivada, quando alguém disse (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: >-
            Pede o catálogo INTEIRO de ferramentas: o roteador de ferramentas do
            Jev (ADR 0179) é pulado nesta chamada. O engine o liga quando repete
            um passo cujo menu restrito fez o modelo responder sem chamar
            ferramenta.
      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: O dev agent que a segura.
      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 do dev agent que reivindicou a tarefa.
        blocked:
          type: boolean
          example: false
          description: Tarefa bloqueada esperando uma decisão ou uma dependência.
        blockedReason:
          type: object
          example: null
          nullable: true
        blockedOrigin:
          type: string
          enum:
            - infra
            - modelo
            - codigo
            - politica
          example: null
          nullable: true
          description: >-
            A ORIGEM do bloqueio (ADR 0020/0038), quando conhecida. `null` em
            todo bloqueio da Fase 4a — só o QA Lead (Fase 8b) a preenche.
        gateStatus:
          type: string
          enum:
            - awaiting_qa
            - awaiting_secops
            - awaiting_user
          example: null
          nullable: true
          description: >-
            Onde a PR da tarefa está no pipeline de gates: QA, depois SecOps,
            depois o usuário. `null` enquanto não há PR aberta.
        gateCorrectionCount:
          type: number
          example: 0
          description: Quantas rodadas de correção os gates já pediram.
        module:
          type: object
          example: api
          nullable: true
          description: >-
            O módulo a que a tarefa pertence, atribuído pelo Dev Lead no plano
            de execução aprovado (RN-678). Só o dev agent desse módulo a
            reivindica. `null` enquanto nenhum plano atribuiu um — tarefa assim
            só é reivindicável quando a história tem exatamente um módulo.
        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: Já desserializado; o engine despacha para a ferramenta pelo `name`.
      required:
        - id
        - name
        - arguments
    ToolRoutingResponseDto:
      type: object
      properties:
        modelo:
          type: string
          example: typesafe/jev-1.13
        ofertadas:
          type: number
          example: 9
          description: Quantas ferramentas o agente tinha neste passo.
        menuAntes:
          description: Os nomes das ferramentas ANTES do roteador.
          type: array
          items:
            type: string
        menuDepois:
          description: >-
            Os nomes das ferramentas oferecidas ao modelo de chat (o catálogo
            inteiro em qualquer queda).
          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: A ferramenta anterior da mesma execução.
        aplicado:
          type: boolean
          description: O Jev respondeu E o menu saiu menor que o catálogo.
        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: Custo REAL da resposta do Jev (`usage.cost`), em micro-USD.
        gastoNaoRegistrado:
          type: boolean
          description: >-
            `true` quando o Jev cobrou mas a linha de `token_usage` não pôde ser
            gravada.
      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 do usuário que vira o titular. Tem de já ser `owner` deste
            workspace.
      required:
        - userId
    TransitionSessionDto:
      type: object
      properties:
        status:
          type: string
          enum:
            - created
            - active
            - closing
            - closed
            - closed_abnormally
          example: closing
          description: >-
            Estado alvo. A máquina de estados recusa salto inválido com 409 — de
            `created` só se vai para `active`, e não há volta de estado
            terminal.
      required:
        - status
    UnblockTaskInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        agentId:
          type: string
          example: dev-api
          description: O dev agent cuja falha transitória agendou a retentativa.
      required:
        - projectId
        - agentId
    UnreadCursorDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        afterSeq:
          type: number
          example: 41
          minimum: 0
          description: >-
            Último `seq` que ESTE navegador já viu neste projeto. Devolve
            eventos com `seq` estritamente maior. `0` quer dizer "nunca vi nada"
            e traz o começo da sessão mais recente.
      required:
        - projectId
        - afterSeq
    UnreadEventsDto:
      type: object
      properties:
        cursors:
          maxItems: 200
          description: >-
            Um cursor por projeto a consultar. Lista VAZIA devolve lista vazia —
            "não pedi nada" não é "me dê tudo". Projeto de outro workspace é
            ignorado em silêncio, porque o cursor vem do armazenamento local de
            quem chama e pode trazer sobras de um workspace antigo.
          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: Preço por milhão de tokens de ENTRADA, em micro-USD.
        outputPricePerMillionMicros:
          type: number
          example: 10000000
          description: Preço por milhão de tokens de SAÍDA, em micro-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. Único dentro do workspace.
        maxConsecutiveBlocked:
          type: number
          example: 3
          description: >-
            Circuit breaker (Fase 12b — RN-047): quantas tarefas consecutivas
            terminando bloqueadas param o dev agent do módulo em idle_tripped.
            Omitido usa o padrão do domínio (DEFAULT_MAX_CONSECUTIVE_BLOCKED).
            Vale da PRÓXIMA ativação de execução em diante — não afeta agentes
            já rodando.
        storyPromotion:
          type: string
          enum:
            - manual
            - auto
          example: auto
          description: >-
            Quem promove uma história de `draft` para `ready` (Fase 12c —
            RN-048). `auto` (padrão de projeto novo desde a RN-659): história
            completa já nasce `ready`, sem passo humano. `manual`: o PO propõe e
            VOCÊ decide, na aba Backlog. Mudar o padrão não reescreveu projeto
            existente nenhum. As validações de domínio
            (DoD/DoR/RF/regra/módulos) são as MESMAS nos dois modos; o modo só
            muda quem dispara.
        language:
          type: string
          example: pt-BR
          maxLength: 35
          description: >-
            O idioma do PROJETO (RN-619) — artefatos compartilhados e turnos sem
            autor humano o usam. Qualquer código BCP-47 que o servidor
            reconhece, gravado na forma canônica; um não reconhecido é 400.
            Omitido, o projeto começa com o idioma de resposta efetivo de quem o
            criou (RN-618: escolha da conta > detecção confirmada > idioma da
            interface).
    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): uma pergunta aberta que o usuário JÁ respondeu —
            só então uma história `ready` (sem tarefa em execução) pode ser
            corrigida.
      required:
        - projectId
    UpdateStoryTitleDto:
      type: object
      properties:
        title:
          type: string
          example: Register a user
          description: Aparado; de 1 a 200 caracteres.
      required:
        - title
    UpdateUserPreferencesDto:
      type: object
      properties:
        locale:
          type: string
          enum:
            - pt-BR
            - en
            - ru
            - zh-Hans
          example: en
          description: O idioma da interface. Fechado à lista — não é BCP-47 livre.
        responseLanguage:
          type: string
          example: es
          maxLength: 35
          description: >-
            O idioma em que os agentes respondem (RN-618): `automatico` ou
            QUALQUER código BCP-47 cujo idioma o servidor reconhece — lista
            aberta, ao contrário de `locale`. Gravado na forma canônica (`pt-br`
            vira `pt-BR`). Código não reconhecido é um 400 que nomeia a regra.
            Nunca muda `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. Único no sistema e visível na URL.
    UpsertBudgetDto:
      type: object
      properties:
        limitUsd:
          type: number
          example: 50
          minimum: 0
          description: >-
            Teto em DÓLARES — o único lugar do contrato que fala em dólares,
            porque é o que uma pessoa digita. A api o converte para micro-USD, e
            as respostas voltam em micro-USD.
        policy:
          type: string
          enum:
            - block
            - allow
          example: block
          description: >-
            `block` recusa a chamada que estouraria o teto; `allow` a deixa
            passar e só avisa.
      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: >-
            A chave em texto puro. É cifrada por envelope encryption antes de
            tocar o banco e NUNCA volta em resposta nenhuma — nem cifrada. O
            teto de 512 caracteres é proteção contra payload absurdo, não
            validação de formato: se a chave presta quem responde é o provider,
            em `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: Cobre chaves de LLM e tokens de 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` (toda conta começa assim) ou o código BCP-47 escolhido
            explicitamente na página da Conta.
        detectedLanguage:
          type: string
          nullable: true
          example: null
          description: >-
            O idioma detectado nas suas próprias mensagens E confirmado por você
            — nunca uma detecção não confirmada. `null` até existir uma
            confirmação.
        detectedLanguageConfirmedAt:
          type: string
          format: date-time
          nullable: true
        effectiveResponseLanguage:
          description: >-
            O que vale hoje FORA de qualquer sessão, e de onde veio: escolha da
            conta > detecção confirmada > idioma da interface.
          allOf:
            - $ref: '#/components/schemas/IdiomaEfetivoResponseDto'
      required:
        - locale
        - responseLanguage
        - detectedLanguage
        - detectedLanguageConfirmedAt
        - effectiveResponseLanguage
    VerifyEmailDto:
      type: object
      properties:
        token:
          type: string
          description: Token de uso único recebido por e-mail.
      required:
        - token
    WorkspaceComPapelResponseDto:
      type: object
      properties:
        workspace:
          $ref: '#/components/schemas/WorkspaceResponseDto'
        role:
          type: string
          enum:
            - viewer
            - developer
            - maintainer
            - owner
          example: maintainer
          description: O papel de QUEM CHAMA neste workspace.
      required:
        - workspace
        - role
    WorkspaceMemberComUsuarioResponseDto:
      type: object
      properties:
        userId:
          type: string
          example: 01JC4Z0000USUARIO0000000002
        role:
          type: string
          enum:
            - viewer
            - developer
            - maintainer
            - owner
          example: maintainer
          description: >-
            O papel de WORKSPACE — o que todo projeto do workspace herda a menos
            que uma linha de projeto o sobreponha (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: >-
            Hierarquia linear: owner > maintainer > developer > viewer. Cada
            papel inclui as permissões dos seguintes.
        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: Único no sistema; é o que aparece na URL.
        createdBy:
          type: string
          example: 01JC4Z0000USUARIO0000000001
        toolRouterEnabled:
          type: boolean
          example: true
          description: >-
            Se o passo de roteamento de ferramentas pelo Jev (ADR 0179) roda
            neste workspace. Ligado por padrão; só age quando o modelo do turno
            é do OpenRouter, então `true` num workspace sem OpenRouter não faz
            nada.
        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: Dono das chaves que financiaram este gasto (RN-058).
        dias:
          type: number
          example: 30
          description: Janela deslizante, em dias.
        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: >-
            Por MODELO. Dois providers servindo o mesmo nome caem na mesma linha
            — separá-los reintroduziria o eixo de credencial com outro nome.
          type: array
          items:
            $ref: '#/components/schemas/SpendLinhaResponseDto'
        porProvider:
          description: >-
            Por PROVIDER (ADR 0076, RN-186). Este eixo fala de CREDENCIAL e por
            isso só existe aqui, numa rota que exige `owner` (RN-060) — a
            resposta do membro não o tem, e não é um filtro que o esconde: é o
            tipo do repositório que o recusa com escopo de ator (RN-187).
          type: array
          items:
            $ref: '#/components/schemas/SpendLinhaResponseDto'
        porProjeto:
          description: Por PROJETO dentro do workspace.
          type: array
          items:
            $ref: '#/components/schemas/SpendLinhaResponseDto'
        porSessao:
          description: By SESSION (RN-952).
          type: array
          items:
            $ref: '#/components/schemas/SpendLinhaResponseDto'
        porAtor:
          description: >-
            Por ATOR — agente e pessoa na mesma lista, distinguidos por
            `actorKind`.
          type: array
          items:
            $ref: '#/components/schemas/SpendLinhaResponseDto'
        porOwner:
          description: >-
            As linhas de PESSOA (`actor_kind = "user"`) — o bloco que o handoff
            chama de "Por owner", porque pela RN-058 é a chave do owner que
            todas gastam. Quem é o owner fica em `ownerId`. Uma partição de
            `porAtor`, sem consulta extra (RN-188).
          type: array
          items:
            $ref: '#/components/schemas/SpendLinhaResponseDto'
        porAgente:
          description: >-
            As linhas de AGENTE (`actor_kind = "agent"`). A outra metade de
            `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: >-
            Número de projetos do workspace. Não há flag de "ativo" no domínio —
            todo projeto conta.
        agentCount:
          type: number
          example: 6
          description: >-
            Agentes distintos (actorKind=agent) que gastaram tokens neste mês,
            somados em todos os projetos do workspace — inclui as
            subespecialidades das áreas (Fase 8).
        spentMicros:
          type: number
          example: 12500000
          description: >-
            Gasto do mês corrente, em micro-USD, somado por
            token_usage.createdAt.
      required:
        - activeProjects
        - agentCount
        - spentMicros
