openapi: 3.0.0
paths:
  /.well-known/jwks.json:
    get:
      description: One key in normal operation; two during an AUTH_JWT_SECRET rotation.
      operationId: JwksController_jwks
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JwksResponseDto'
      summary: Public access token verification keys
      tags:
        - auth
  /auth/login:
    post:
      description: >-
        A nonexistent email, wrong password, and a locked account all return
        exactly the same 401 response — same body, same 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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: Invalid credentials.
        '403':
          description: Email not yet verified.
      summary: Authenticates and issues the access + refresh pair
      tags:
        - auth
  /auth/logout:
    post:
      description: >-
        Always 204, even for an unknown token — answering 401 here would be a
        token-validity oracle.
      operationId: AuthController_logout_
      parameters: []
      responses:
        '204':
          description: Session ended. The session cookies are cleared.
      summary: Revokes the family of the presented refresh
      tags:
        - auth
  /auth/oauth/{provider}/callback:
    get:
      description: >-
        Success goes to `WEB_ORIGIN/` already with the session cookies set;
        failure goes to `WEB_ORIGIN/login?oauth_error=1`, without detailing the
        reason.
      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: Redirects to the web app.
          headers:
            Location:
              description: Destination on the web app.
              schema:
                type: string
                example: http://localhost:5173/
      summary: Receives the social login OAuth callback
      tags:
        - auth
  /auth/oauth/{provider}/start:
    get:
      description: >-
        The `state` goes signed by HMAC with its OWN purpose — never the git
        connection-to-project flow's (see ADR 0084).
      operationId: AuthController_oauthStart
      parameters:
        - name: provider
          required: true
          in: path
          schema:
            enum:
              - github
              - gitlab
            type: string
      responses:
        '302':
          description: Redirects to the provider.
          headers:
            Location:
              description: The provider's authorization URL.
              schema:
                type: string
                example: https://github.com/login/oauth/authorize?…
        '400':
          description: Provider outside `github`/`gitlab`.
      summary: Redirects to the OAuth provider for social login
      tags:
        - auth
  /auth/refresh:
    post:
      description: >-
        The presented refresh is consumed. Re-presenting an already-rotated
        token revokes the WHOLE family and records a security event — even when
        the cause was a client double-submit.
      operationId: AuthController_refresh_
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessaoResponseDto'
        '401':
          description: Refresh invalid, expired, or already used.
      summary: Rotates the refresh and issues a new pair
      tags:
        - auth
  /auth/register:
    post:
      description: >-
        Responds 202 for both a new address and an already-registered one — the
        response doesn't reveal whether the account exists. In the second case
        nothing is created and the address's owner gets a notice.
      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: Password fails the minimum policy.
        '403':
          description: Registration closed (AUTH_REGISTRATION_ENABLED=false).
      summary: Creates an account and sends the verification email
      tags:
        - auth
  /auth/request-password-reset:
    post:
      description: >-
        Responds 202 for both a known and an unknown address. It is also the
        path for whoever was imported from Keycloak and hasn't set a password
        yet.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
      summary: Requests the password reset link
      tags:
        - auth
  /auth/reset-password:
    post:
      description: >-
        Revokes ALL of the user's sessions. Does not issue tokens: whoever
        resets the password is sent to login, so that compromising the email
        doesn't equal taking over the account in a single step.
      operationId: AuthController_resetPassword
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ResetPasswordDto'
      responses:
        '204':
          description: >-
            Password set. ALL of the user's live sessions are revoked along with
            it.
        '400':
          description: Link invalid/expired, or password fails the policy.
      summary: Sets the new password from the 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: Email verified; login already works.
        '400':
          description: >-
            Link invalid, expired, or already used — all three with the same
            response.
      summary: Confirms the email with the single-use token
      tags:
        - auth
  /gates:
    get:
      description: >-
        Declarative index of the flow gates (ADR 0054). The screen derives from
        here which steps exist and who judges each one, instead of repeating the
        list in code — which is how it used to go stale unnoticed.
      operationId: GatesController_listar
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GatesResponseDto'
        '401':
          description: No token, expired token, or invalid signature.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: The registry of active gates
      tags:
        - gates
  /git/oauth/{provider}/callback:
    get:
      description: >-
        Public because whoever arrives is the user's BROWSER coming from the
        provider, without an api session. It isn't unrestricted: the `state` is
        verified by HMAC and without a valid one the call is refused. Never
        responds with JSON — always redirects, because a raw error body in a
        browser navigation would be a terrible experience.
      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: >-
            Success goes to `WEB_ORIGIN/projects/:id?git=connected`; failure
            goes to `WEB_ORIGIN/git-error`. The error does NOT leak the reason
            in the URL.
          headers:
            Location:
              description: Destination on the web app.
              schema:
                type: string
                example: http://localhost:5173/projects/01JC…?git=connected
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project does not exist.
      summary: Receives the OAuth return and redirects to the web app
      tags:
        - git
  /health:
    get:
      description: >-
        Runs a `select 1`. Reveals only whether the database responds; no
        topology or version. Public because the caller is kubelet, which carries
        no token.
      operationId: HealthController_check
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthStatusResponseDto'
        '503':
          description: >-
            The database did not respond. The body carries the message in
            `details`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthStatusResponseDto'
      summary: Readiness — the process can actually serve traffic
      tags:
        - infrastructure
  /internal/first-account:
    post:
      description: >-
        For the one-line installer, which runs where no mail transport is
        configured: the generated `.env` carries no mail variable,
        `MAIL_TRANSPORT` falls to `log`, and normal registration waits on an
        e-mail that never arrives. The account is born verified because whoever
        runs the installer already proved something STRONGER than controlling a
        mailbox — they control the machine, the `.env` and the Docker daemon.
        Normal registration is untouched. Refuses with 409 when the installation
        has ANY user: the condition is about the installation, not about this
        e-mail, so a migration restore silences the step by the same test. The
        password is never generated here and never stored anywhere but as an
        argon2id hash; the personal workspace (RN-410) is born in the same
        transaction.
      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: >-
            Malformed e-mail, or a password the DOMAIN policy refuses — the same
            code registration calls, never a second rule.
        '403':
          description: Service token missing or different from the shared one.
        '409':
          description: The installation already has a user. Nothing was created.
      security:
        - service-token: []
      summary: Creates the FIRST account of an installation, already verified
      tags:
        - internal
  /internal/gates:
    get:
      description: >-
        The registry DESCRIBES, it does not execute: no gate becomes enforced
        because of it, and changing a field here changes no behavior at all.
        Each gate says where the proof that it passed lives (`evidencia`),
        because not every proof is in the event log.
      operationId: InternalGatesController_ler
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GateRegistryResponseDto'
        '403':
          description: Service token missing or different from the shared one.
      security:
        - service-token: []
      summary: Reads the declarative gate registry
      tags:
        - internal
  /internal/graph/prompt-templates:
    post:
      description: >-
        If a version with the SAME hash already exists for the same `name`, no
        new version is created — the existing one is returned (the idempotency
        hit does not change the HTTP status: always 201, for simplicity — the
        body is identical to what would have been created).
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '403':
          description: Service token missing or different from the shared one.
        '503':
          description: >-
            Neo4j not configured or unreachable — no fallback possible for
            reading/writing a template.
      security:
        - service-token: []
      summary: Publishes a prompt template version, idempotent by hash
      tags:
        - internal
  /internal/graph/prompt-templates/{name}:
    get:
      description: >-
        A specific `version` looks up by equality; omitted, it looks up the most
        recent `active` version. 404 if the template (or the requested version)
        does not exist.
      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 missing or different from the shared one.
        '404':
          description: Template does not exist, or lacks the requested version.
        '503':
          description: >-
            Neo4j not configured or unreachable — no fallback possible for
            reading/writing a template.
      security:
        - service-token: []
      summary: Looks up a prompt template by name
      tags:
        - internal
  /internal/machine-device-keys:
    post:
      description: >-
        For the one-line installer, right after it creates the first account:
        the local agent needs a credential, and the only one that existed was
        bound to a PROJECT — in an installation that has none yet. The pair is
        generated ON THE MACHINE and only the public half arrives here; write
        the returned `id` into the private JWK as `kid` (RN-475), which is the
        only link between the file on disk and the public half on the server.
        There is no `userId` in the body ON PURPOSE: the owner is the
        installation's SOLE user, resolved by the api, so holding the service
        token never means choosing whose credential to mint. Registering
        REPLACES: the owner's active machine keys are revoked in the same
        transaction, so a reinstalled machine works and a thousand machine keys
        cannot exist. Project keys (ADR 0118) are never touched.
      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: >-
            The JWK is not a PUBLIC Ed25519 key — malformed JSON, wrong
            `kty`/`crv`, missing `x`, or carrying `d` (that is the private half,
            and it is refused by name rather than stored).
        '403':
          description: Service token missing or different from the shared one.
        '409':
          description: >-
            The installation has no user, or more than one. Nothing was written.
            This route belongs to the moment of installation and goes quiet for
            good once the installation has a team.
      security:
        - service-token: []
      summary: Registers the MACHINE device key of a fresh installation
      tags:
        - internal
  /internal/models/sync:
    post:
      description: >-
        Never fails because of one provider: each one answers for itself in the
        report, with the reason it was skipped and the origin of the failure
        when there was one. A provider that did not respond is SKIPPED, not
        marked unavailable.
      operationId: InternalModelsController_sync
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SyncModelCatalogResponseDto'
        '403':
          description: Service token missing or different from the shared one.
      security:
        - service-token: []
      summary: Syncs the model catalog across all providers
      tags:
        - internal
  /internal/projects/{projectId}/backlog:
    get:
      description: >-
        The SAME epic → story → task tree as the Backlog tab, through the same
        use case (three reads per project, never N+1). It is with this that the
        PO sees an orphan epic and a story with no task instead of re-creating
        what already exists (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 missing or different from the shared one.
      security:
        - service-token: []
      summary: >-
        The project's backlog as a tree, for the PO to read what was already
        written
      tags:
        - internal
  /internal/projects/{projectId}/business-rules:
    get:
      description: >-
        Every `artifact.business_rule` from the project's sessions — not just
        the current session's, which was the ceiling of the PO's kickoff context
        — with the full `description` and which stories already cite each rule.
        `uncoveredCount` is the pending item: a rule no story covers (RN-164). A
        project with no rule at all responds `200` with an empty list: "no rule
        captured yet" is not an error.
      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 missing or different from the shared one.
      security:
        - service-token: []
      summary: The project's business rules, with coverage, for the PO to read
      tags:
        - internal
  /internal/projects/{projectId}/container-exec:
    post:
      description: >-
        Called only by the engine, when `Engine.Actions.TerminalExecutor`
        decided the command belongs inside the real container (`execution_mode:
        container` with a REGISTERED `running` row). Proxies to
        `ContainerBrokerPort.exec` (ADR 0130/0134) — the last of the broker's
        five operations to gain a real caller. `sucesso: false` in the response
        — never a thrown error — is the NORMAL shape for a refused or
        unreachable broker: the container may have died or been removed from
        outside between the registered `running` row and this call (RN-486,
        registered and observed never merge), and that is a command failure, not
        a transport error.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '403':
          description: Service token missing or different from the shared one.
      security:
        - service-token: []
      summary: Runs a terminal command INSIDE the project real container (RN-492)
      tags:
        - internal
  /internal/projects/{projectId}/container-spec:
    get:
      description: >-
        Project identity, execution mode and the Architect's current image
        decision. The broker revalidates all of it before handing anything to
        the daemon — reading from here is not the same as trusting it, and the
        refusal names the field. No ABSOLUTE path is returned: the bind source
        is resolved by the daemon against the HOST filesystem, and a path from
        inside the api container would silently mount an empty folder. What
        travels is `localizacao` (RN-503) — which of the two broker roots to
        use, plus the relative segment that root does not cover.
      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 missing or different from the shared one.
        '404':
          description: Project not found.
      security:
        - service-token: []
      summary: What the broker needs to compose the container spec itself
      tags:
        - internal
  /internal/projects/{projectId}/git-remote:
    get:
      description: >-
        Returns the clean origin (no credential embedded) and, for a remote
        provider, the workspace OWNER's token (RN-058) decrypted on the fly.
        Whoever consumes it injects the token per invocation and NEVER writes it
        to a file — `.git/config` sits inside the folder where RN-075 grants the
        dev agent auto-approved read access.
      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 missing or different from the shared one.
        '404':
          description: >-
            Project with no provisioned repository, or the workspace owner has
            no registered credential for the repository provider.
      security:
        - service-token: []
      summary: The project's working remote, to fetch and push
      tags:
        - internal
  /internal/projects/{projectId}/mirror-sync-result:
    post:
      description: >-
        Called only by the engine, after the runner pushes `mirror_sync_result`
        over the channel — the SAME path as `workspace_confirm` (runner →
        channel → engine → api), never a second mechanism: the engine reports,
        it does not write the table. This is TELEMETRY, not a domain event:
        never `session_events` (a mirror round has no session and `session_id`
        is `NOT NULL`, the same reasoning that made `rag_searches` a table) and
        never a `proposed_action` (the mirror write is configuration the user
        declared, not an agent asking to act). `ok` is what decides the outcome
        — never the presence of a count or of a message: a round that copied 0
        files is normal and must not be indistinguishable from one that never
        ran. Recording never breaks what it measures: the copy is already done
        when this is called, and the engine only logs a refusal here. A project
        whose destination was cleared meanwhile STILL records — the round
        happened, and its error is usually what explains what went wrong.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Project does not exist.
      security:
        - service-token: []
      summary: The local agent reports the outcome of a mirror round (RN-517)
      tags:
        - internal
  /internal/projects/{projectId}/module-contracts:
    get:
      description: >-
        One entry per module of the CURRENT module_map: what it consumes
        (`dependeDe`, the map's `dependsOn`) and what it exposes (`expoe`, from
        the Architect's current `artifact.module_contracts`, or `null` when none
        was declared). It is what a dev agent reads instead of opening another
        module's worktree (ADR 0200). A project with no contract responds `200`
        with `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 missing or different from the shared one.
      security:
        - service-token: []
      summary: The contracts between modules, for a dev agent to read (RN-684)
      tags:
        - internal
  /internal/projects/{projectId}/product-metrics:
    get:
      description: >-
        The SAME report as the `analise:funil` script (ADR 0089) — session →
        commit → PR → merge funnel, real lead time and real deployment frequency
        — through the same pure functions and the same query
        (`apps/api/src/application/services/funil-metrics.ts`), so the two never
        diverge from the same fact. Closes `docs/fluxo.yml` (role `po`, input
        `metricas-de-produto`, previously `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 missing or different from the shared one.
        '404':
          description: Project does not exist.
      security:
        - service-token: []
      summary: >-
        The project's delivery funnel and partial DORA metrics, for the PO to
        read
      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 missing or different from the shared one.
        '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 missing or different from the shared one.
        '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: >-
        Called only by the engine, after a runner connects and sends
        `workspace_confirm` over the channel. The runner is the SOURCE OF TRUTH
        for the path — the api overwrites `workspacePath` with what it reported,
        after re-validating it lexically (system root/overlap with Brabo are
        still forbidden even coming from the runner). Idempotent: reconnecting
        with the SAME path writes nothing again.
      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: Lexically invalid path, or the project is not in "runner" mode.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Project does not exist.
      security:
        - service-token: []
      summary: The runner confirms the path of a "runner" project (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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '403':
          description: Service token missing or different from the shared one.
      security:
        - service-token: []
      summary: Removes QA instances whose gate is no longer in flight (RN-937)
      tags:
        - internal
  /internal/rag/feedback:
    post:
      description: >-
        Same use case as the human route — no second judgement path. An unknown
        `searchId`/`chunkId` is a 400 that the engine turns into an error
        tool-result for the model to correct (RN-061), never a 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: >-
            Unknown `searchId` for this project, or a `chunkId` that was not
            among that search hits.
        '403':
          description: Service token missing or different from the shared one.
      security:
        - service-token: []
      summary: The agent's vote on one retrieved chunk (RN-480)
      tags:
        - internal
  /internal/rag/search:
    post:
      description: >-
        Same engine as `POST /projects/:projectId/rag/search` (human route),
        projected into the format the engine's `rag_search` tool expects.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '403':
          description: Service token missing or different from the shared one.
      security:
        - service-token: []
      summary: Hybrid search over the project's RAG index, for the engine's tool
      tags:
        - internal
  /internal/sessions/{sessionId}/actions:
    post:
      description: >-
        The ONLY door through which an agent touches git, terminal, or spend.
        `permissions.json` decides at creation, and `deny` beats any autonomy
        granted to the agent.
      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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Proposes an action with an external effect on behalf of an agent
      tags:
        - internal
  /internal/sessions/{sessionId}/anamnese-context:
    get:
      description: >-
        Everything in one call, mirroring the Psychologist's context. Members
        already come EXCLUDING whoever opted out of being profiled, and the
        user's decisions within the window come through here because they aren't
        in the event log. The engine reads the events themselves straight from
        Postgres — carrying them over HTTP would be more expensive without being
        more correct.
      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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Assembles the context for an Anamnesis round
      tags:
        - internal
  /internal/sessions/{sessionId}/anamnese-window:
    post:
      description: >-
        Records an anamnese run with zero profiles so the next round starts at
        windowTo — without it, the window never advanced and every tick paid for
        a new round over the same 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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Closes the Anamnese window of a round that emitted no profile
      tags:
        - internal
  /internal/sessions/{sessionId}/c4-diagram:
    post:
      description: >-
        The artifact IS the `artifact.c4_diagram` event: immutable, versioned,
        and with an author, alongside `artifact.module_map`. Requires a current
        module_map — without one there is no Container level to draw (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: Missing `system_name`, invalid actor, or no current module_map.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Generates a new version of the C4 diagram (Context + Container)
      tags:
        - internal
  /internal/sessions/{sessionId}/delegations:
    post:
      description: >-
        `completed` (with the verdict), `failed` (with the origin), or
        `dispensed` (with the justification) — the lead never calls this route
        with a halfway outcome: every delegation is born here already resolved.
      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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Records the outcome of an area delegation
      tags:
        - internal
  /internal/sessions/{sessionId}/dev-context:
    get:
      description: >-
        A single call with everything the prompt needs: the whole story, the
        resolved business rules, and the applicable ADRs. `module` restricts the
        ADRs to that dev's module; an ADR with no declared module is
        CROSS-CUTTING and always comes in. Omitting `module` brings the whole
        collection, which is what the QA and SecOps gates want when reusing this
        same context.
      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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Assembles the full context of a task for the 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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Creates a backlog epic
      tags:
        - internal
  /internal/sessions/{sessionId}/events:
    get:
      description: >-
        Used to REHYDRATE a conversational agent's history and to read the
        artifacts its kickoff needs. The equivalent human route is protected by
        RBAC; this one, by the service token. `latest=true` returns the TAIL
        (still in ascending `seq`) and ignores `afterSeq`; `types` restricts the
        page to those event types. The page is capped at 200 either way (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: Fetches the tail of the log; ignores `afterSeq`.
          schema:
            example: 'true'
            type: string
        - name: types
          required: false
          in: query
          description: >-
            Comma-separated event types (at most 20). Only events of these types
            count toward `limit`.
          schema:
            example: artifact.product_brief,artifact.business_rule
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaginaDeEventosResponseDto'
        '400':
          description: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Paginates the session's event log for the engine
      tags:
        - internal
    post:
      description: >-
        Same use case and same atomic `seq` assignment as the human route. The
        Psychologist's hypotheses do NOT go through here: they have their own
        route, with evidence validation.
      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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Appends an event to the session's log, on behalf of an agent
      tags:
        - internal
  /internal/sessions/{sessionId}/gates/verdict:
    post:
      description: >-
        The response's `nextAction` is what the engine obeys: `correct` returns
        to dev, `run_secops` advances the gate, `done` releases it to the user,
        `await_user` parks the cycle until the open question is answered
        (RN-860, no correction counted), and `blocked` means the correction cap
        ran out. MERGE is never automatic — `done` only means it's the human's
        turn.
      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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Records QA's or SecOps's verdict on a task's PR
      tags:
        - internal
  /internal/sessions/{sessionId}/handoffs:
    post:
      description: >-
        Born as `offered`. Who accepts is a PERSON, via the human route — an
        agent doesn't activate an agent. Two exceptions. The Creative→PO offer
        carrying the `product_brief` that an "I'm ready — the need is validated"
        click asked for is accepted on behalf of whoever clicked, with that
        person as the actor and `implicito` in the payload (RN-658, ADR 0185).
        The PO's handoff to the Arquiteto is accepted by the SYSTEM when the
        backlog is covered (at least one business rule, none without a story)
        and the repository is `local` with no git credential in the project
        (RN-660, ADR 0186); `aceiteAutomatico` says whether it happened and, if
        not, why. Either way the response then carries `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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
        '409':
          description: >-
            `agente_ja_ativo` (ADR 0182, RN-635): the target is already active
            in a non-closed session of the project. The `message` is the text
            the agent reads as the tool result.
      security:
        - service-token: []
      summary: Offers a handoff from one agent to another
      tags:
        - internal
  /internal/sessions/{sessionId}/hypotheses:
    post:
      description: >-
        Each hypothesis needs to cite events that EXIST in this session —
        made-up evidence is rejected with 400, and that is what separates a
        hypothesis from an opinion. The session's previous round becomes
        superseded.
      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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
        '409':
          description: A current analysis already exists for this session.
      security:
        - service-token: []
      summary: Records an analysis round and its hypotheses
      tags:
        - internal
  /internal/sessions/{sessionId}/infra-artifacts/{prActionId}/files:
    get:
      description: >-
        The content comes from the `proposed_action`'s own payload: an infra
        artifact never touches a worktree, same as 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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Returns the files of an infra PR for the gates to read
      tags:
        - internal
  /internal/sessions/{sessionId}/infra-context:
    get:
      description: >-
        The current module_map plus the relevant infrastructure ADRs, plus the
        Architect's module routing (ADR 0131) — the candidates the Infra Lead
        elects among when proposing 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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Assembles the InfraAgent's initial context
      tags:
        - internal
  /internal/sessions/{sessionId}/infra-gates/verdict:
    post:
      description: Same pipeline and same state machine as dev PRs.
      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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Records QA's or SecOps's verdict on an infra PR
      tags:
        - internal
  /internal/sessions/{sessionId}/instruction-patches:
    post:
      description: >-
        Becomes a `proposed_action`, not a direct write: changing an agent's
        behavior is an external effect and goes through the same approval
        pipeline as everything else. This closes the hypothesis → patch loop.
      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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Proposes a patch to an agent's instruction file
      tags:
        - internal
  /internal/sessions/{sessionId}/llm-turn:
    post:
      description: >-
        Metering is MANDATORY: every turn records `token_usage`, and that is
        what makes the budget mean something. Doesn't record any event — the
        engine narrates the event log. A provider failure comes back in `error`
        with 200, because the accounting in `usage` remains valid: the turn
        spent even while failing.
      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: Invalid body.
        '403':
          description: Budget exceeded with `policy=block`, or invalid service token.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Runs a metered LLM turn, with tool support
      tags:
        - internal
  /internal/sessions/{sessionId}/llm-turn-stream:
    post:
      description: >-
        Same semantics as `llm-turn`, delivered frame by frame. The `done` frame
        carries the `usage` — without it the turn would have come out with no
        accounting.
      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: Stream of frames up to `done` or `error`.
          content:
            text/event-stream:
              schema:
                $ref: '#/components/schemas/LlmTurnStreamEventResponseDto'
        '400':
          description: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Runs an LLM turn with the response streamed
      tags:
        - internal
  /internal/sessions/{sessionId}/max-parallel-proposals:
    post:
      description: >-
        Refuses to propose a cap equal to or lower than the current one: the
        Anamnesis runs periodically, and would re-propose the same thing every
        round, filling with noise a queue the user needs to read.
      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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Proposes raising an area's parallelism cap
      tags:
        - internal
  /internal/sessions/{sessionId}/module-contracts:
    post:
      description: >-
        The artifact IS the `artifact.module_contracts` event: immutable,
        versioned, and with an author, alongside `artifact.module_map`. Each
        call carries the WHOLE list and replaces the previous version. What a
        module consumes is not written here: it is the current module_map's
        `dependsOn`.
      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: >-
            Empty list, repeated module, module outside the current module_map
            (or no module_map), a module with an empty or oversized `expoe`, an
            item with an unknown `tipo`, or a missing/oversized `assinatura`.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Declares a new version of the contracts between modules
      tags:
        - internal
  /internal/sessions/{sessionId}/module-map:
    post:
      description: >-
        The history is immutable: each publication is one more version, and the
        current one is the one with the highest `version`. A dependency CYCLE
        between modules gets the map REJECTED with 400 — the graph needs to be
        acyclic.
      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: Dependency cycle between modules.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Publishes a new version of the module_map
      tags:
        - internal
  /internal/sessions/{sessionId}/module-routing:
    post:
      description: >-
        The artifact IS the `artifact.module_routing` event: immutable,
        versioned, and with an author, alongside
        `artifact.module_map`/`artifact.project_image`/`artifact.c4_diagram`.
        Each item is validated with the same rule as `choose_project_image`
        (explicit tag/digest, `latest` rejected, non-trivial `rationale`); a
        module name outside the current module_map, an empty list, or a repeated
        module are all rejected with 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: >-
            Empty list, repeated module, module outside the current module_map,
            or a candidate image that fails `choose_project_image`'s rule (no
            explicit tag, `latest`, or short `rationale`).
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Routes each module of the current module_map to a candidate image
      tags:
        - internal
  /internal/sessions/{sessionId}/pending-work:
    get:
      description: >-
        Queried by the engine's `SessionServer` on heartbeat timeout. `pending:
        true` makes the timeout get rescheduled instead of closing, and the
        `motivo` goes into the engine's log — a session that doesn't close
        without saying why is undiagnosable.
      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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Does the session have pending work that blocks closing it?
      tags:
        - internal
  /internal/sessions/{sessionId}/proficiency:
    post:
      description: >-
        A competency outside the catalog is REJECTED, and so is evidence that
        cites an event from another project — both validations exist because the
        model gets both things wrong.
      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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Records the proficiency profiles derived in the round
      tags:
        - internal
  /internal/sessions/{sessionId}/project-image:
    post:
      description: >-
        The artifact IS the `artifact.project_image` event: immutable,
        versioned, and with an author. Revising means issuing a new version,
        never overwriting. While there isn't one yet, the project's container
        does not come up and the Code tab responds 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: >-
            Image with no explicit tag (or `latest`), `rationale` too short,
            `network` outside {none, egress}, or a resource above the cap.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Sets the project's container image
      tags:
        - internal
  /internal/sessions/{sessionId}/psychologist-context:
    get:
      description: >-
        `alreadyAnalyzed` is what gives the automatic path IDEMPOTENCY: with
        `true` the worker short-circuits without spending a token. The prior
        hypotheses come along so the round doesn't repeat itself.
      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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Assembles the context for a Psychologist round
      tags:
        - internal
  /internal/sessions/{sessionId}/semantic-duplicate-check:
    post:
      description: >-
        Embeds the title and the titles of the project rules (the 100 most
        recent) with the RAG embedding model and warns at cosine ≥ the
        threshold. Never refuses. Without an embedding provider the check is
        SKIPPED with the reason, also narrated in the event log. The embedding
        spend is metered as its own `token_usage` row (actor
        `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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Warns when a just-written business rule looks like an existing one
      tags:
        - internal
  /internal/sessions/{sessionId}/stories:
    post:
      description: >-
        `businessRuleIds` is what feeds the rule→story coverage. Each id has to
        reference an `artifact.business_rule` event that EXISTS — validation
        rejects a made-up id. The response also carries `semanticDuplicate`
        (RN-681): an embedding-based WARNING against the project stories, never
        a refusal.
      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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: >-
        Creates a story with functional/non-functional requirements, DoD, DoR,
        and covered rules
      tags:
        - internal
  /internal/sessions/{sessionId}/stories/{storyId}/archive:
    post:
      description: >-
        The story and its tasks leave the backlog, coverage, plan and claim; the
        row stays and `backlog.story_archived` records it, with the PO as actor
        (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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Story is not in this project.
        '409':
          description: >-
            Named refusal (`reason`): `historia_arquivada`, `historia_nao_draft`
            or `historia_com_tarefa_em_execucao`.
      security:
        - service-token: []
      summary: Archives a draft story
      tags:
        - internal
  /internal/sessions/{sessionId}/stories/{storyId}/complete:
    post:
      description: >-
        `businessRuleIds` is ADDED to the rules already linked; non-empty lists
        replace the current ones and omitted lists stay. After writing, the
        story is promoted by the SAME criterion as story creation (`ready` in
        `auto` mode, `proposedReady` in `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: A business_rule_id does not reference an existing rule.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Story is not in this project.
        '409':
          description: Story is no longer `draft`.
      security:
        - service-token: []
      summary: Completes an existing draft story instead of re-creating it
      tags:
        - internal
  /internal/sessions/{sessionId}/stories/{storyId}/update:
    post:
      description: >-
        Only a `draft` story with no task in execution; writes
        `backlog.story_updated` with the PO as actor (RN-727). A `ready` one too
        when `questionId` cites an open question the user already answered
        (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: No field, or an empty title.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Story is not in this project.
        '409':
          description: >-
            Named refusal (`reason`): `historia_arquivada`, `historia_nao_draft`
            or `historia_com_tarefa_em_execucao`.
      security:
        - service-token: []
      summary: Corrects the title and/or description of a draft story
      tags:
        - internal
  /internal/sessions/{sessionId}/story-modules:
    post:
      description: >-
        This is the cross-validation: a module that doesn't exist in the current
        map becomes an architecture pending item instead of slipping through
        unnoticed.
      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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Associates a story with the modules it touches
      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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Creates a task inside a story
      tags:
        - internal
  /internal/sessions/{sessionId}/tasks/{taskId}/block:
    post:
      description: >-
        There is no automatic unblocking: whoever unblocks it is a person, via
        the human route. That is what stops an agent from spinning indefinitely.
      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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Marks the task as blocked, with the reason
      tags:
        - internal
  /internal/sessions/{sessionId}/tasks/{taskId}/gate/open:
    post:
      description: Called when the dev agent's PR is ready for review.
      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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Opens the task's gate pipeline, starting with 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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
        '409':
          description: Invalid transition, or another agent's task.
      security:
        - service-token: []
      summary: Moves the task's state as work progresses
      tags:
        - internal
  /internal/sessions/{sessionId}/tasks/{taskId}/unblock:
    post:
      description: >-
        Called by the engine retry job (up to 3 times, after 1, 3 and 10 min).
        Same use case as the human unblock, with a `system` actor; a task that
        is no longer blocked is left untouched.
      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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Unblocks a task after a transient network/provider failure
      tags:
        - internal
  /internal/sessions/{sessionId}/tasks/claim:
    post:
      description: >-
        ATOMIC by design: with several dev agents on the same module, two
        concurrent claims cannot return the same task. With no task available,
        returns empty instead of an error.
      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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Atomically claims the module's next claimable task
      tags:
        - internal
  /internal/sessions/{sessionId}/tasks/in-progress-by-dev:
    get:
      description: >-
        Not blocked and outside the PR gates; empty unless the session is an
        execution session. Used by the engine when stopping an execution, to
        block the ones no live dev agent holds.
      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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Lists the project's in-progress tasks held by a dev agent
      tags:
        - internal
  /internal/sessions/{sessionId}/termination:
    post:
      description: >-
        Only terminations the api did NOT cause reach here — crash, kill,
        `heartbeat_timeout`, defensive shutdown. A stop planned by the api
        itself doesn't go through here: the engine already learned of it via the
        outbox, and reporting it back would be an echo.
      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: Invalid body.
        '403':
          description: Service token missing or different from the shared one.
        '404':
          description: Session, project, or resource not found.
      security:
        - service-token: []
      summary: Reports that the session process terminated in the engine
      tags:
        - internal
  /live:
    get:
      description: >-
        Does NOT touch the database, on purpose. If it did, a slow Postgres
        would restart ALL replicas at once and turn degradation into a total
        outage. The pod is pulled from the load balancer by `/health`.
      operationId: HealthController_live
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthStatusResponseDto'
      summary: Liveness — the process still serves requests
      tags:
        - infrastructure
  /llm/provider-capabilities:
    get:
      description: >-
        The PROVIDER layer of the capabilities (ADR 0041) for the nine
        providers, read from the same instances that serve the calls. A
        capability is only `true` when proven against the real API. The screen
        reads `routingPreference` from here before offering a routing preference
        on a binding (ADR 0166).
      operationId: ProviderCapabilitiesController_list
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ProviderCapabilitiesResponseDto'
        '401':
          description: No token, expired token, or invalid signature.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Lists what each LLM provider can do, independent of the model
      tags:
        - llm
  /metrics:
    get:
      description: >-
        Not JSON: it's `text/plain` in exposition format. Public out of
        necessity (the scraper carries no token) and restricted by NETWORK — the
        production Ingress blocks this path at the edge and the NetworkPolicy
        only allows the monitoring namespace.
      operationId: MetricsController_scrape
      parameters: []
      responses:
        '200':
          description: Metrics in exposition format.
          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: Exposes metrics in Prometheus exposition format
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project doesn't exist or is invisible to the caller.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Returns a project by 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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project doesn't exist or is invisible to the caller.
        '409':
          description: A project with this slug already exists in the workspace.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Changes the project's name or slug
      tags:
        - projects
    delete:
      description: Takes its sessions, event log, and proposed actions along with it.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project doesn't exist or is invisible to the caller.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Removes the project
      tags:
        - projects
  /projects/{projectId}/actions:
    get:
      description: >-
        `status` is `pending` (the default) or `failed`. `actionType` filters by
        type — e.g. `git_merge`, for the PRs tab to find the merge proposal for
        a PR without knowing which session created it. `status=failed` REQUIRES
        `actionType` and lists the actions whose execution failed — the PRs tab
        reads the last refused merge of each PR from it (a merge conflict
        carries `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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Lists the PENDING actions of the whole project, across any session
      tags:
        - actions
  /projects/{projectId}/agent-areas:
    get:
      description: >-
        The project is born with the three areas (RN-094), so the list comes
        back full even before there is a `module_map`. What activating execution
        adds are the MEMBERS of the dev area, one per module.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or task not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: The project's agent areas, with each lead's cap
      tags:
        - execution
  /projects/{projectId}/agent-areas/{key}/budget:
    put:
      description: >-
        Requires `maintainer`, same reason as the parallelism cap: this is an
        INDEPENDENT, additive check next to the project and session budgets (ADR
        0110) — not a cascade, and not a replacement for either. The limit comes
        in as DOLLARS and is converted to micro-USD server-side, same convention
        as `BudgetsController`. `null` clears the cap.
      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` is neither `null` nor a number >= 0.'
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or task not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Sets (or clears) an area's spend cap
      tags:
        - execution
  /projects/{projectId}/agent-areas/{key}/max-parallel:
    patch:
      description: >-
        Requires `maintainer`, not `developer`, for the same reason as
        activating execution: raising the cap is deciding how much the product
        can spend without asking. Applies to the NEXT requests — whatever is
        already awaiting your decision keeps waiting.
      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` is not an integer >= 1.'
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or task not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Changes an area's parallelism cap
      tags:
        - execution
  /projects/{projectId}/agent-autonomy:
    get:
      description: >-
        Only the explicitly recorded rules. An agent with no row here falls back
        to the project's `permissions.json` default.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Lists the autonomy granted to each agent
      tags:
        - actions
    put:
      description: >-
        Upsert by (agent, type). It does NOT override `permissions.json`: a
        pattern already in `deny` stays blocked no matter how much autonomy is
        granted here. `actionType: "*"` is "auto mode" (RN-153) — autonomy for
        ANY action type of this agent; a specific rule recorded afterwards still
        wins over the wildcard for that type.
      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: Rule recorded. No body.
        '400':
          description: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Sets an agent's autonomy for an action type
      tags:
        - actions
  /projects/{projectId}/agent-bindings/{agentSlug}:
    get:
      description: >-
        Agent → area → project → workspace cascade, WITHOUT a session: it is the
        agent's configuration on the project, not that of a specific
        conversation. `origin: "agent"` means this agent DIVERGED from its
        area's default; any other origin means it inherits.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the scope.
        '404':
          description: Scope not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Resolves which model an agent uses, and where it came from
      tags:
        - llm
    put:
      description: >-
        Applies to every session in this project that has no binding of its own,
        and only to those — before ADR 0064 the binding was global and reached
        other projects too. This is how a cheap model is given to the
        Psychologist and an expensive one to the Architect in the same project,
        and also how an agent DIVERGES from its area's default.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the scope.
        '404':
          description: Scope not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Pins an agent's model in THIS project
      tags:
        - llm
    delete:
      description: >-
        DELETES the agent's binding — it does not copy the area's model into it.
        Copying would look the same on screen and is not: it would become a
        copy, and the area's next change would silently leave this agent behind.
        404 when it already inherits.
      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: The agent went back to inheriting. No body.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the scope.
        '404':
          description: Scope not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Makes the agent go back to inheriting the area's model
      tags:
        - llm
  /projects/{projectId}/agent-costs:
    get:
      description: >-
        A SLIDING 30-day window, not the current calendar month: that is the
        label the screen shows, and with a calendar month the estimate would
        plunge on the 1st from the page turning over, not from a change in
        usage. Only `actor_kind = agent` counts (RN-038). An agent that never
        ran does not appear in the list — the screen shows a dash for it, which
        is different from 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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project or session not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Breaks down the project spend by agent, over the last 30 days
      tags:
        - llm
  /projects/{projectId}/agents/{agent}/instruction-versions:
    get:
      description: >-
        Most recent first, each already carrying its diff against the previous
        one, computed server-side.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, event, or version does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Lists an agent's instruction versions
      tags:
        - anamnesis
  /projects/{projectId}/agents/{agent}/instruction-versions/{version}/rollback:
    post:
      description: >-
        The history is immutable: restoring deletes nothing, it CREATES a new
        version with the old content. Requires `maintainer` because it changes
        the agent's behavior from that point on — same calibration as the 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: Version to restore.
          schema:
            example: 2
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RollbackResponseDto'
        '400':
          description: Version is not a positive integer.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, event, or version does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Restores a previous version of an agent's instruction
      tags:
        - anamnesis
  /projects/{projectId}/anamnese/run:
    post:
      description: >-
        Requires `maintainer` for the same reason as the Psychologist
        reanalysis: it runs the ToolLoop and spends real budget.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, event, or version does not exist.
        '429':
          description: Rate limit per user or per IP.
        '503':
          description: >-
            The Anamnese is disabled globally by the user's decision (not a bug)
            — body with `reason: "anamnese_disabled"`.
      security:
        - bearer: []
      summary: Runs the Anamnese now, without waiting for the tick
      tags:
        - anamnesis
  /projects/{projectId}/architecture:
    get:
      description: >-
        The current map is the one with the highest `version` (history is
        immutable). The pending items are the story↔map cross-validation: a
        story with no module, or pointing at a module that doesn't exist.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Returns the current module_map, the ADRs, and the pending items
      tags:
        - backlog
  /projects/{projectId}/area-bindings/{areaKey}:
    get:
      description: >-
        The default the lead and the area's subagents share. `origin: "area"`
        means someone chose it for this area; any other origin means the area
        itself inherits from the project or the 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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the scope.
        '404':
          description: Scope not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Resolves what an area's default model is, and where it came from
      tags:
        - llm
    put:
      description: >-
        Applies to the lead and to every subagent of the area that has no
        binding of its own (RN-102). Requires `maintainer` because it reaches
        the whole area at once, and choosing a model is deciding spend — the
        same reason as the parallelism cap.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the scope.
        '404':
          description: Scope not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Sets an area's default model
      tags:
        - llm
    delete:
      description: >-
        The area stops having its own default and starts inheriting from the
        project (or the workspace). Agents that diverged keep diverging: their
        binding is a different one, and deleting this one cannot decide for
        them.
      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: The area went back to inheriting. No body.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the scope.
        '404':
          description: Scope not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Makes the area go back to inheriting the project's model
      tags:
        - llm
  /projects/{projectId}/backlog:
    get:
      description: >-
        Nested and complete, in a single call. ARCHIVED stories (RN-727) are not
        listed. Backlog is written by agents, through the `/internal/*` routes.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Returns the epic → story → task tree
      tags:
        - backlog
  /projects/{projectId}/budget:
    get:
      description: Values in micro-USD. Requires `maintainer` — it is cost information.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project or session not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Reads the project budget and how much has already been spent
      tags:
        - llm
    put:
      description: >-
        The limit comes in as DOLLARS and is converted to micro-USD. With
        `policy=block` the call that would exceed the cap is REFUSED, not just
        recorded.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project or session not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Sets the project spend cap
      tags:
        - llm
  /projects/{projectId}/code/blame:
    get:
      description: >-
        Foundation for the declared blame pending item (PHASE 26b) — the UI that
        annotates the editor line by line doesn't exist yet. `truncated` warns
        when the file went past the annotated-lines cap.
      operationId: CodeController_blame
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: path
          required: true
          in: query
          description: Path of the file to annotate.
          schema:
            type: string
        - name: ref
          required: false
          in: query
          description: Branch, tag, or sha. Absent uses the repository's default branch.
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CodeBlameResponseDto'
        '400':
          description: >-
            Path outside the project scope (RN-095), malformed ref, or a search
            term that is too short or too long.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: >-
            Project without a provisioned repository, or a nonexistent
            ref/path/PR.
        '429':
          description: Rate limit per user or per IP.
        '501':
          description: >-
            The project's provider does not declare the operation's capability —
            `listTree`, `pullRequestDiff`, `blame`, `pullRequestsList`, or
            `branchesDetailed`. A capability is only declared once proven by the
            contract suite, so this is a legitimate response, not a defect.
      security:
        - bearer: []
      summary: Annotates every line of a file with the commit that touched it
      tags:
        - git
  /projects/{projectId}/code/branches:
    get:
      description: >-
        Foundation for the rich dropdown (PHASE 26b) — today the ref selector is
        a plain text field. `ahead`/`behind` are relative to the repository's
        DEFAULT branch; `null` when the provider can't compute it (an honest
        degradation, not an error). It is NOT `listBranches` (which the Gitflow
        bootstrap uses) — it is its own operation, because enriching costs an
        extra call to the provider PER 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: >-
            Path outside the project scope (RN-095), malformed ref, or a search
            term that is too short or too long.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: >-
            Project without a provisioned repository, or a nonexistent
            ref/path/PR.
        '429':
          description: Rate limit per user or per IP.
        '501':
          description: >-
            The project's provider does not declare the operation's capability —
            `listTree`, `pullRequestDiff`, `blame`, `pullRequestsList`, or
            `branchesDetailed`. A capability is only declared once proven by the
            contract suite, so this is a legitimate response, not a defect.
      security:
        - bearer: []
      summary: Lists branches with ahead/behind and the associated open PR
      tags:
        - git
  /projects/{projectId}/code/file:
    get:
      description: >-
        UTF-8, cut at the byte cap with `truncated: true` when it's exceeded. A
        path that escapes the project folder is refused with 400 by the central
        check in RN-095 — never with 404, which would invite probing.
      operationId: CodeController_file
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: path
          required: true
          in: query
          description: File path.
          schema:
            type: string
        - name: ref
          required: false
          in: query
          description: Branch, tag, or sha.
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CodeFileResponseDto'
        '400':
          description: >-
            Path outside the project scope (RN-095), malformed ref, or a search
            term that is too short or too long.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: >-
            Project without a provisioned repository, or a nonexistent
            ref/path/PR.
        '429':
          description: Rate limit per user or per IP.
        '501':
          description: >-
            The project's provider does not declare the operation's capability —
            `listTree`, `pullRequestDiff`, `blame`, `pullRequestsList`, or
            `branchesDetailed`. A capability is only declared once proven by the
            contract suite, so this is a legitimate response, not a defect.
      security:
        - bearer: []
      summary: Returns a file's content
      tags:
        - git
  /projects/{projectId}/code/pull-requests:
    get:
      description: >-
        Foundation for the navigable list (PHASE 26b) — today the diff is only
        reachable by a known id (e.g. coming from Approvals); this route
        resolves how to reach the id without having to know it by heart.
        `truncated` warns when the list went past the per-call cap.
      operationId: CodeController_pullRequests
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: state
          required: false
          in: query
          description: Absent lists all states.
          schema:
            enum:
              - open
              - merged
              - closed
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CodePullRequestListResponseDto'
        '400':
          description: >-
            Path outside the project scope (RN-095), malformed ref, or a search
            term that is too short or too long.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: >-
            Project without a provisioned repository, or a nonexistent
            ref/path/PR.
        '429':
          description: Rate limit per user or per IP.
        '501':
          description: >-
            The project's provider does not declare the operation's capability —
            `listTree`, `pullRequestDiff`, `blame`, `pullRequestsList`, or
            `branchesDetailed`. A capability is only declared once proven by the
            contract suite, so this is a legitimate response, not a defect.
      security:
        - bearer: []
      summary: Lists the repository's PRs/MRs
      tags:
        - git
  /projects/{projectId}/code/pull-requests/{pullRequestId}/diff:
    get:
      description: >-
        A null `patch` distinguishes the case where the provider didn't deliver
        the text (binary, or a patch too large) from the case where the text
        came back empty. Collapsing the two would make the screen say a changed
        binary didn't change. The `truncated` field warns when the file list
        went past the cap.
      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: >-
            Path outside the project scope (RN-095), malformed ref, or a search
            term that is too short or too long.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: >-
            Project without a provisioned repository, or a nonexistent
            ref/path/PR.
        '429':
          description: Rate limit per user or per IP.
        '501':
          description: >-
            The project's provider does not declare the operation's capability —
            `listTree`, `pullRequestDiff`, `blame`, `pullRequestsList`, or
            `branchesDetailed`. A capability is only declared once proven by the
            contract suite, so this is a legitimate response, not a defect.
      security:
        - bearer: []
      summary: Returns the diff of a PR, normalized across providers
      tags:
        - git
  /projects/{projectId}/code/search:
    get:
      description: >-
        Search is NOT a git contract operation: it is composed over the tree and
        the content, and so is the only read whose cost grows with the size of
        the repository. Three budgets cap it — directories walked, files opened,
        and matches returned — and `truncated` says it stopped. `filesScanned`
        shows the cost it incurred.
      operationId: CodeController_search
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: q
          required: true
          in: query
          description: Term, 2 to 200 characters.
          schema:
            type: string
        - name: ref
          required: false
          in: query
          description: Branch, tag, or sha.
          schema:
            type: string
        - name: path
          required: false
          in: query
          description: Subtree to search in. Narrowing this is what avoids truncation.
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CodeSearchResponseDto'
        '400':
          description: >-
            Path outside the project scope (RN-095), malformed ref, or a search
            term that is too short or too long.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: >-
            Project without a provisioned repository, or a nonexistent
            ref/path/PR.
        '429':
          description: Rate limit per user or per IP.
        '501':
          description: >-
            The project's provider does not declare the operation's capability —
            `listTree`, `pullRequestDiff`, `blame`, `pullRequestsList`, or
            `branchesDetailed`. A capability is only declared once proven by the
            contract suite, so this is a legitimate response, not a defect.
      security:
        - bearer: []
      summary: Searches text in the repository, with a budget
      tags:
        - git
  /projects/{projectId}/code/tree:
    get:
      description: >-
        Not recursive, by contract design: the tab navigates on demand, and
        requesting the whole tree of a large repository is exactly the traffic
        amplifier this phase forbids. `truncated` warns when the level went past
        the entry cap.
      operationId: CodeController_tree
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: ref
          required: false
          in: query
          description: Branch, tag, or sha. Absent uses the repository's default branch.
          schema:
            type: string
        - name: path
          required: false
          in: query
          description: Directory to list. Absent or empty is the root.
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CodeTreeResponseDto'
        '400':
          description: >-
            Path outside the project scope (RN-095), malformed ref, or a search
            term that is too short or too long.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: >-
            Project without a provisioned repository, or a nonexistent
            ref/path/PR.
        '429':
          description: Rate limit per user or per IP.
        '501':
          description: >-
            The project's provider does not declare the operation's capability —
            `listTree`, `pullRequestDiff`, `blame`, `pullRequestsList`, or
            `branchesDetailed`. A capability is only declared once proven by the
            contract suite, so this is a legitimate response, not a defect.
      security:
        - bearer: []
      summary: Lists ONE level of the repository tree
      tags:
        - git
  /projects/{projectId}/container:
    get:
      description: >-
        While the status is `sem_decisao`, the project container does not come
        up and the Code tab responds 409 — that's the RN-105 gate. Seeing the
        state requires the same permission as seeing the project, so `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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: State of the project's container (the current image decision)
      tags:
        - projects
  /projects/{projectId}/container/lifecycle:
    get:
      description: >-
        `null` when the project was never provisioned — the common case today,
        because no real orchestrator transitions this table yet (RN-243/RN-267).
        `status`/`imageVersion`/`resources` are what was RECORDED; `observado`
        is what the Docker daemon reports right now, asked through the broker
        (ADR 0130). The two are never fused: a container killed from the outside
        reads as recorded `running` and observed `exited`. When there was no way
        to look, `observado` is `null` and `naoObservado` says why — it never
        inherits the recorded state (RN-468).
      operationId: ContainersController_cicloDeVida
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: The recorded lifecycle, or `null` (never provisioned).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CicloDeVidaDoContainerResponseDto'
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Lifecycle of the project's container (recorded state)
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '429':
          description: Rate limit per user or per 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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Declares the container network (human only)
      tags:
        - projects
  /projects/{projectId}/coverage:
    get:
      description: >-
        Cross-references the project's rules with the stories'
        `businessRuleIds`. A rule with no story at all is a DISCOVERY — a
        pending item for the PO, not an error.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Computes business-rule → stories coverage
      tags:
        - backlog
  /projects/{projectId}/events/{eventId}:
    get:
      description: >-
        Different from the event-by-session route: here the session isn't known.
        This is what makes the profile's evidence chip land on the right event,
        since the Anamnese's window spans several sessions.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, event, or version does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Returns a project event by id, resolving its session
      tags:
        - anamnesis
  /projects/{projectId}/execution-mode:
    put:
      description: >-
        Migrates `execution_mode`/`workspacePath` for a project that ALREADY
        exists (RN-447..450, ADR 0111) — dedicated route, separate from PATCH,
        because it orchestrates more than a column write: it moves
        permissions.json to the new scope root, retires the container lifecycle
        row when leaving `container` mode, and refuses (409) while any dev agent
        of the project is non-idle, since a running agent doesn't re-resolve its
        worktree location on its own.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project doesn't exist or is invisible to the caller.
        '409':
          description: A dev agent of this project is working or stuck right now.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Converts the project's execution mode
      tags:
        - projects
  /projects/{projectId}/execution/activate:
    post:
      description: >-
        Requires `maintainer`, not `developer`, because the dev agents' git
        actions inherit the role of WHOEVER ACTIVATED it in the IAM evaluation —
        activating as developer would leave agents unable to open a PR. Each
        module in the `module_map` gets an agent in an isolated worktree.
      operationId: ExecutionController_activate
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ActivateExecutionDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExecucaoAtivadaResponseDto'
        '400':
          description: >-
            No current `module_map` (the Architect has to define the modules
            first), or the project's stored workspace location is incoherent —
            the `permissions.json` seeded here cannot be derived from it
            (RN-478).
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or task not found.
        '409':
          description: >-
            The project has no repository yet (RN-582): the dev agents work in
            worktrees of it, so nothing starts. The message names what is
            missing — the handoff to the Architect (or, as a second door, to the
            Dev Lead) whose acceptance provisions it, or the provisioning page
            when that handoff was already accepted and left no repository. Also:
            the origin session is `consultiva` and refuses `execution.activated`
            (RN-097). Activating twice is NOT a conflict: it is idempotent by
            `findActiveExecutionSession`, and reactivates inside the same
            session.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Activates the execution phase and starts one dev agent per module
      tags:
        - execution
  /projects/{projectId}/execution/session:
    get:
      description: >-
        The most recent `active` session that already carries
        `execution.activated` — the SAME criterion `activate` uses to decide
        whether to reactivate or create one (RN-139). `null` when no execution
        is in progress. Exists so the Executors tab stops inferring this session
        from the project's most recent one, which shifts from session to session
        with no clue at all in the UI.
      operationId: ExecutionController_getSession
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: '`null` in the body when there is no active execution.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionResponseDto'
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or task not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Returns the project's current execution session, or nothing
      tags:
        - execution
  /projects/{projectId}/git/{provider}/connect:
    get:
      description: >-
        Doesn't redirect: returns the URL for the client to decide when to send
        the browser. The `state` goes signed by HMAC, and that is what prevents
        the callback from being forged.
      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 outside `github`/`gitlab`.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Starts OAuth with the git provider
      tags:
        - git
  /projects/{projectId}/git/{provider}/repository:
    post:
      description: >-
        SYNCHRONOUS: the response only comes back after the whole bootstrap
        (permanent branches, protections, templates) has run — there is no
        worker and no queue behind this. It is IDEMPOTENT AND RESUMABLE, so
        calling it again after a failure resumes from the step that failed.
        Track progress and read the failure reason via `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: Invalid provider, or missing credential.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Creates the repository and runs the Gitflow bootstrap
      tags:
        - git
  /projects/{projectId}/git/{provider}/repository/adopt:
    post:
      description: >-
        Validates access with `getRepo` and produces a PLAN (dry-run) of what
        the bootstrap would do — missing branches, missing protections, files.
        NOTHING is executed on the repository: decide afterward via
        `plan/approve` or `plan/skip`. Re-adopting the same repository converges
        (regenerates the plan without duplicating a row).
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: The repository exists, but the registered credential can't reach it.
        '404':
          description: The repository doesn't exist on the provider — check the identifier.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Adopts an existing repository, without creating anything
      tags:
        - git
  /projects/{projectId}/git/bootstrap:
    get:
      description: >-
        Which step it is on, whether it failed and where, and how many attempts
        there were. The step-by-step detail comes from the `bootstrap.step_*`
        events of the dedicated session.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Returns the Gitflow bootstrap state
      tags:
        - git
  /projects/{projectId}/git/bootstrap/acknowledge-protection-failure:
    post:
      description: >-
        `protect_branches` fails on a private repository on the free plan, and
        the wizard warns about this BEFOREHAND. Without this exit the only
        button was "Try again", which always fails for the same reason — and
        `provision_failed` makes the dashboard redirect the project back to the
        provisioning page, leaving it unreachable forever. Only a failure in
        PROTECTING can be acknowledged: it is the last step and the only one
        whose failure leaves a usable repository. The product's merge lock
        (RN-006) doesn't depend on the provider's protection and keeps applying.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project does not exist.
        '409':
          description: >-
            The bootstrap did not fail, or it failed on a step before protection
            — moving on there would leave the project without a usable
            repository.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Acknowledges the branch-protection failure and moves on
      tags:
        - git
  /projects/{projectId}/git/bootstrap/plan:
    get:
      description: >-
        `decision: null` with a plan present is the state where NOTHING runs —
        waiting for approval or as-is adoption (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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Returns the bootstrap plan and what was decided about it
      tags:
        - git
  /projects/{projectId}/git/bootstrap/plan/approve:
    post:
      description: >-
        Approval is all-or-nothing: approving loose steps would break the
        `dev←main, qa←dev, rc←qa` cascade. What runs is the plan RE-DERIVED at
        execution time — equal to or smaller than what was shown, never larger.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project does not exist.
        '409':
          description: >-
            The plan was regenerated since you last saw it, or it had already
            been decided.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Approves the WHOLE plan and runs the bootstrap
      tags:
        - git
  /projects/{projectId}/git/bootstrap/plan/skip:
    post:
      description: >-
        Records the decision and doesn't touch the repository. The plan stays
        stored as evidence of what was deliberately not applied, and the
        bootstrap cursor is NOT tampered with — no step ran.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project does not exist.
        '409':
          description: >-
            The plan was regenerated since you last saw it, or it had already
            been decided.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Adopts the repository AS IS, dismissing the 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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Returns the project's provisioned repository
      tags:
        - git
  /projects/{projectId}/hypotheses:
    get:
      description: >-
        Each hypothesis cites the events that support it (`evidenceEventIds`) —
        this is what makes it auditable instead of taken on faith.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or hypothesis does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Lists the Psychologist's hypotheses about the agents
      tags:
        - psychologist
  /projects/{projectId}/hypotheses/{hypothesisId}/accept:
    post:
      description: >-
        Moves `proposed` → `accepted`. NOT a `proposed_action`: the Psychologist
        is read-only and nothing here has an external effect.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or hypothesis does not exist.
        '409':
          description: The hypothesis was already decided.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Accepts a hypothesis
      tags:
        - psychologist
  /projects/{projectId}/hypotheses/{hypothesisId}/dismiss:
    post:
      description: Moves `proposed` → `dismissed`. Same reasoning as accepting.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or hypothesis does not exist.
        '409':
          description: The hypothesis was already decided.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Dismisses a hypothesis
      tags:
        - psychologist
  /projects/{projectId}/infra-artifacts:
    get:
      description: >-
        InfraAgent's artifacts (Dockerfile, compose, CI). They go through the
        SAME QA and SecOps gates as dev PRs, with no task or worktree behind
        them.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Lists infra PRs and the gate stage of each one
      tags:
        - backlog
  /projects/{projectId}/instruction-versions:
    get:
      description: >-
        Starts from whoever HAS a version in the project, not from a static
        roster — this is what makes the per-module dev agents (`dev-api`) show
        up.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, event, or version does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Lists the instruction history of every agent in the project
      tags:
        - anamnesis
  /projects/{projectId}/members:
    get:
      description: >-
        Only who was associated HERE, with the role of that association —
        whoever reaches the project through the workspace alone has no row and
        doesn't show up.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project doesn't exist or is invisible to the caller.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Lists the project's members
      tags:
        - projects
    post:
      description: >-
        This role OVERRIDES whatever the person has in the workspace, in both
        directions: associating someone as `viewer` here really does restrict a
        workspace `developer` on this project. Two movements are refused with
        403 and cannot be enabled anywhere: downgrading a workspace `owner`, and
        changing your OWN role — down or up, since self-promotion is the half
        that escalates privilege.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: >-
            Insufficient role on the project, OR one of the two caps (the target
            is a workspace `owner`; the target is the caller and the requested
            role differs from their current effective one).
        '404':
          description: Project doesn't exist or is invisible to the caller.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Associates a user with the project
      tags:
        - projects
  /projects/{projectId}/members/{userId}:
    delete:
      description: >-
        Removes only the PROJECT association. Whoever has a role in the
        workspace keeps seeing the project through inheritance — and because the
        project role OVERRIDES the workspace one in both directions, that
        inheritance can be LOWER than what the row granted. Removing your own
        row is therefore refused with 403 whenever the net effect is a downgrade
        (including when there is no workspace role at all); removing it when the
        workspace holds the same role, and removing anyone else, still work.
      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: Association removed. No body.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: >-
            Insufficient role on the project, OR the caller is removing their
            own row and would end up with a lower role than they have today.
        '404':
          description: Project doesn't exist or is invisible to the caller.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Disassociates a user from the project
      tags:
        - projects
  /projects/{projectId}/mirror-path:
    put:
      description: >-
        The absolute path, ON THE USER MACHINE, where the local agent copies the
        work to (RN-515, ADR 0147) — a folder OUTSIDE the mounted base, which is
        the whole reason the mirror exists. Per project and never global: one
        global destination would land project B artifacts in project A's folder,
        and the user would find out from the contents, not from an error.
        `mirrorPath: null` CLEARS it and turns the mirror off — the key is
        required, omitting it is a 400, because a body that omits the field
        would be indistinguishable from asking to clear. `null` is the NORMAL
        state of a project. `maintainer`, the same minimum as `execution-mode`
        and `projects-base`: this route talks about a path on the operator's own
        filesystem. It validates ONLY the LEXICAL shape and refuses, with 400, a
        destination inside `workspacePath` or containing it (both directions of
        the same loop), and any destination at all on a `container` project. It
        never touches disk: the API cannot see the machine where the destination
        will live. Writing the mirror is NOT a proposed_action — it is
        configuration the user declared, not an agent asking to act.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project doesn't exist or is invisible to the caller.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Declares (or clears) the project's mirror destination
      tags:
        - projects
  /projects/{projectId}/mirror-state:
    get:
      description: >-
        Telemetry the local agent pushed over the channel after copying the work
        to the user folder (ADR 0147, point 7) — never the event log, because a
        mirror round has no session and `session_events.session_id` is `NOT
        NULL` (the same reasoning that made `rag_searches` a table). THREE
        answers that never collapse into one (RN-088): `never` (no round ever
        reported), `synced` (the last round copied — `filesCopied` may be `0`,
        which means "looked and there was nothing to copy") and `failed`. A
        failure never erases the last successful sync, and a success never
        erases the last error: which one is CURRENT comes from comparing the two
        timestamps, so the screen can say "failing since today, last good copy
        was yesterday with 412 files". `lastDestination` is FROZEN — it diverges
        from `mirrorPath` after someone changes the destination, because the
        grant travels in the join and only changes when the runner reconnects
        (RN-516). `viewer`, the same minimum as reading the project, which
        already carries `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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project doesn't exist or is invisible to the caller.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: What the last mirror round did (RN-517)
      tags:
        - projects
  /projects/{projectId}/model-binding:
    get:
      description: >-
        This scope's RAW binding — `null` if the project only inherits from the
        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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the scope.
        '404':
          description: Scope not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Reads the project model binding
      tags:
        - llm
    put:
      operationId: ModelBindingsController_setProjectBinding
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SetModelBindingDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelBindingResponseDto'
        '400':
          description: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the scope.
        '404':
          description: Scope not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Sets the project's model
      tags:
        - llm
  /projects/{projectId}/model-bindings/resolved:
    get:
      description: >-
        Batch form of `GET .../agent-bindings/:agentSlug` and `GET
        .../area-bindings/:areaKey`: each requested key gets EXACTLY the value
        its single-key route answers (same cascade, same origins, `null` when no
        level has a model). It exists so the Settings tab reads 20 resolved
        bindings with one request instead of 20 — the rate limit is per USER.
        Keys are comma-separated, deduplicated, and a malformed key or more than
        64 keys in total is 400, never silently dropped.
      operationId: ModelBindingsController_getResolvedBindings
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: agents
          required: false
          in: query
          description: Comma-separated agent slugs.
          schema:
            example: criativo,po,dev-lead
            type: string
        - name: areas
          required: false
          in: query
          description: Comma-separated area keys.
          schema:
            example: dev,qa,infra
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BindingsResolvidosEmLoteResponseDto'
        '400':
          description: A malformed key, or more keys than the batch cap.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the scope.
        '404':
          description: Scope not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Resolves the model of several agents and areas in one read
      tags:
        - llm
  /projects/{projectId}/models:
    get:
      description: >-
        The `local` category are Ollama's, which spend no money; `cloud` are the
        paid API ones. A model the workspace has not activated does not appear
        here, but stays in the historical costs — to see it use the catalog
        route. The list is the project's OWNER workspace: the same model can be
        on in one workspace and off in the neighboring one (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: No token, expired token, or invalid signature.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Lists the models active IN THE PROJECT's WORKSPACE
      tags:
        - llm
  /projects/{projectId}/open-questions:
    get:
      description: >-
        Status comes from the event log: open is an `artifact.open_question`
        without `open_question.answered`. Newest first, capped at 50 with
        `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: No token, expired token, or invalid signature.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Lists the agents' open questions of the project (RN-858)
      tags:
        - backlog
  /projects/{projectId}/open-questions/{questionId}/answer:
    post:
      description: >-
        Only the author of the session where the question was born answers.
        Writes a `decision_record` (actor: the user, with `questionId`) and
        `open_question.answered`, and asks the engine to resume the parked QA
        gate of each affected task (RN-860), best effort.
      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: No answer, or a suggestion accepted where there is none.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: 'Not the session author — body with `reason: "so_o_autor_da_sessao"`.'
        '404':
          description: Question is not in this project.
        '409':
          description: 'Already answered — `reason: "pergunta_ja_respondida"`.'
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Answers an open question (RN-858)
      tags:
        - backlog
  /projects/{projectId}/permissions:
    get:
      description: >-
        It's the physical file at the root of the project's workspace, not a
        database column.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project doesn't exist or is invisible to the caller.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Reads the project's permissions.json
      tags:
        - projects
    put:
      description: >-
        Replaces all three lists at once. `deny` still beats `allow` in
        evaluation — there's no way to allow something denied through here.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project doesn't exist or is invisible to the caller.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Rewrites the project's permissions.json
      tags:
        - projects
  /projects/{projectId}/personal-access-tokens:
    get:
      description: Never includes the raw token — only what was already issued.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Lists your own Personal Access Tokens for this project
      tags:
        - projects
    post:
      description: >-
        The raw token (`brb_...`) appears only in THIS response — it is not
        recoverable later, only the hash is stored. Use it in
        `--token`/`BRABO_ACCOUNT_TOKEN` of `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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Issues a Personal Access Token for the local runner
      tags:
        - projects
  /projects/{projectId}/personal-access-tokens/{tokenId}:
    delete:
      description: >-
        Idempotent — revoking again is not an error. Since RN-685 (ADR 0201) it
        also DROPS the runner connections opened with THIS token, and only those
        — the same user's device key stays up —, and voids its still-unused
        tickets. The engine being down does NOT make the revocation fail.
      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 revoked. No body.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Revokes one of your own Personal Access Tokens
      tags:
        - projects
  /projects/{projectId}/personal-access-tokens/{tokenId}/admin:
    delete:
      description: >-
        Incident response — a departed dev with a leaking token (RN-427).
        Idempotent — revoking again is not an error. Since RN-685 (ADR 0201) it
        also DROPS the runner already connected with this token, and only it.
      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 revoked. No body.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Revokes the Personal Access Token of ANY user in the project
      tags:
        - projects
  /projects/{projectId}/personal-access-tokens/all:
    get:
      description: >-
        The `maintainer` view for incident response (RN-427) — includes the
        owner of each token. Never includes the raw token.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Lists ALL Personal Access Tokens of the project, from any user
      tags:
        - projects
  /projects/{projectId}/proficiency:
    get:
      description: >-
        The user's OWN profile by default; the aggregated team view only for
        whoever administers the project. A competency profile is data ABOUT the
        person, and the least surprising default is seeing your own.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, event, or version does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Returns the proficiency profile
      tags:
        - anamnesis
  /projects/{projectId}/proficiency/me:
    delete:
      description: >-
        Only the caller's OWN profile, never someone else's. The opt-out comes
        along because without it the next round would re-derive everything and
        the deletion would be cosmetic. It is `viewer` on purpose: profiling
        covers all members, so requiring `developer` would leave a profiled
        viewer unable to delete their own.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, event, or version does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Deletes your own profile and records the opt-out
      tags:
        - anamnesis
  /projects/{projectId}/proficiency/me/opt-in:
    post:
      description: >-
        Undoes the opt-out. Profiles start being derived again from the next
        round on.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, event, or version does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Re-enables the user's own profiling
      tags:
        - anamnesis
  /projects/{projectId}/psychologist/analyses:
    get:
      description: >-
        `costMicros` is summed from the round's `token_usage`. This is what
        makes the cost difference between light and heavy triage VISIBLE,
        instead of just asserted — the two tiers use genuinely different models.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or hypothesis does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Lists the analysis rounds with tier and real cost
      tags:
        - psychologist
  /projects/{projectId}/psychologist/status:
    get:
      description: >-
        Read-only — has no side effect. `enabled: false` means the pause is a
        PRODUCT decision (not a bug): existing analyses and hypotheses aren't
        touched by it.
      operationId: PsychologistController_status
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PsychologistStatusResponseDto'
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or hypothesis does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Reports whether the Psychologist can run a NEW analysis today
      tags:
        - psychologist
  /projects/{projectId}/rag/coverage:
    get:
      description: >-
        Real count (never estimated) of `.md` files under `docs`/`docs/adr` in
        the project's repository against how many have a chunk, and the
        project's sessions against how many have a chunk. Does not include "N
        minutes ago" for those three scopes — there is no per-scope indexing
        timestamp column, and a guessed number would lie (same rule as ADR 0042
        for model rating). `local` (RN-455) is the one exception:
        `lastAttachedAt` is a real `MAX(created_at)` over that scope, because a
        browser upload has no repository state to compare against.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: 'Index coverage: indexed files/sessions against the real total'
      tags:
        - rag
  /projects/{projectId}/rag/feedback:
    post:
      description: >-
        The only signal of TRUTH the RAG measurement has: latency and
        degradation rate say whether the search RAN, never whether it was RIGHT.
        Same role as `search` — whoever can read the result is who can judge it.
        Re-voting the same chunk of the same search overwrites your own vote
        rather than adding a second one (unique per actor), so the metric
        measures accuracy and not enthusiasm.
      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: >-
            Unknown `searchId` for this project, or a `chunkId` that was not
            among that search hits — a vote without a rank would be a number
            without meaning.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Judges one hit of one search as useful or irrelevant (RN-480)
      tags:
        - rag
  /projects/{projectId}/rag/local:
    post:
      description: >-
        Text content already read by the browser (`<input webkitdirectory>`),
        never a host path — that distinction is the whole point of ADR 0113.
        Idempotent full rebuild of the `local` scope: every call deletes and
        recreates it from the files sent THIS time, so re-uploading is the
        resync mechanism. Rejects (400) over the file-count/total-bytes caps
        instead of truncating silently — this is a one-shot user gesture with a
        folder picker in front of it, not a background scan. An individual
        oversized or unrecognized-extension file is only skipped, never fails
        the batch.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: >-
        Attaches a local folder (browser upload) as read-only reference (RN-455,
        ADR 0113)
      tags:
        - rag
  /projects/{projectId}/rag/reindex:
    post:
      description: >-
        "Reindex now" from the Chat RAG panel. Deletes and recreates the chunks
        of the three honest scopes (RN-219) from the CURRENT state — there is no
        automatic watcher on push/session close (decision recorded in ADR
        0079/0080). Can take a while on projects with many sessions: it runs one
        indexing pass per session, each possibly calling the embedding provider
        in batch.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Reindexes the project's docs/ADR/sessions (idempotent full rebuild)
      tags:
        - rag
  /projects/{projectId}/rag/search:
    post:
      description: >-
        Combines cosine similarity (pgvector) with lexical `ts_rank` (tsvector),
        each from an independent query, merged by weighted sum and cut off by
        the threshold (ADR 0080). `vectorAvailable: false` warns when the
        embedding provider did not respond and the search ran with only the
        lexical signal — it never pretends to have run the full hybrid.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Hybrid search (vector + lexical) over the project's indexed chunks
      tags:
        - rag
  /projects/{projectId}/run-instructions:
    get:
      description: >-
        RN-852 (ADR 0213): the current `artifact.run_instructions`, emitted when
        an `open_infra_pr` with `run` executes. DECLARED by the Infra agent,
        never verified on the reader's machine. `null` when there is none.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Returns how to run the deliverable on your machine
      tags:
        - backlog
  /projects/{projectId}/runner-device-keys:
    get:
      description: >-
        Nobody revokes what they cannot see (RN-519). Includes the already
        REVOKED ones — dropping the row would make the screen claim the key
        never existed. Never returns the public JWK, and the api never saw the
        private one. A null `lastUsedAt` is the sign of an ORPHAN key:
        registered and never used by any runner. Since RN-543 it also includes
        the caller's MACHINE keys (`especie: "maquina"`, null `projectId`),
        which serve this project without belonging to it — without them in the
        list, a machine key would be invisible on every screen.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Lists your own device keys for this project
      tags:
        - projects
  /projects/{projectId}/runner-device-keys/{deviceKeyId}:
    delete:
      description: >-
        Idempotent — revoking again is not an error. Since RN-520 it also DROPS
        the live connection: before, revoking only prevented a NEW ticket, and
        an already connected runner kept running approved commands. Since RN-685
        (ADR 0201) the target is the KEY: only the connections opened with it
        drop — in every project, if it is a machine key —, and another runner of
        the same user, connected with a PAT or another key, stays up. Its
        still-unused tickets are voided. The engine being down or no runner
        connected does NOT make the revocation fail.
      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: Key revoked. No body.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Revokes one of your own device keys
      tags:
        - projects
  /projects/{projectId}/runner-ticket:
    post:
      description: >-
        Authenticates `connect/3` of the Phoenix socket `/runner` (topic
        `terminal:<projectId>`, role "runner") — it is NOT the reused JWT, and
        this route itself only accepts a Personal Access Token instead of the
        session JWT (ADR 0105). 30s TTL and single use. Refuses with 400 if the
        project is not in "runner" mode (RN-421): the runner has nothing to
        serve in a project whose code lives in the managed container.
      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: Project is not in "runner" mode.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Issues a single-use ticket for the local runner to connect
      tags:
        - projects
  /projects/{projectId}/sessions:
    get:
      description: >-
        Each item carries `technical`: `true` only for the session opened by
        repository provisioning (linked from `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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role in the project.
        '404':
          description: Project or session does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Lists the project's sessions
      tags:
        - sessions
    post:
      description: >-
        The session is born in `created` and is the container for everything
        agents do: the event log, proposed actions and the token budget all hang
        off it. `kind` is REQUIRED and gets recorded: `consultiva`
        (consultative) only converses, `criativa` (creative) produces and is the
        only one that accepts `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: Body without `kind`, or with a value outside the allowed list.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role in the project.
        '404':
          description: Project or session does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Opens a session in the project
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role in the project.
        '404':
          description: Project or session does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Returns a session by id
      tags:
        - sessions
    patch:
      description: >-
        The name is a navigation label and does NOT replace the id's hashtag —
        screens compose the two, and without a name they degrade to just the
        hashtag. `null` or a blank string clears the name. There is no way to
        change `kind`: it is the creation intent, and changing it later would
        turn it into state.
      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: Body without `name`, or a name above the character limit.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role in the project.
        '404':
          description: Project or session does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Sets or clears the session's friendly name
      tags:
        - sessions
  /projects/{projectId}/sessions/{sessionId}/actions:
    get:
      description: >-
        Ordered by `seq`; use `nextCursor` as `afterSeq`. `latest=true` fetches
        the TAIL instead (the newest `limit` actions, still ascending) and
        ignores `afterSeq`. `status=pending` keeps only the actions waiting for
        a decision — with `latest`, the newest pending ones, so a session with
        more than 200 actions does not push a new pending one out of the window
        (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: Fetches the tail of the session; ignores `afterSeq`.
          schema:
            example: 'true'
            type: string
        - name: status
          required: false
          in: query
          description: Only `pending` is supported.
          schema:
            example: pending
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaginaDeAcoesResponseDto'
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or action not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Paginates the proposed actions in the session
      tags:
        - actions
    post:
      description: >-
        The action is evaluated against the project's `permissions.json` on
        creation. The response's `resolvedPolicy` says what the policy decided:
        `auto_approve` comes out already executable, `require_approval` waits
        for a human decision, and `deny` is born rejected. There is no path that
        executes without going through here.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or action not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Proposes an action with external effect
      tags:
        - actions
  /projects/{projectId}/sessions/{sessionId}/actions/{actionId}/approve:
    post:
      description: >-
        Applies only to this action. The decision stays in the event log with
        the id of who decided.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or action not found.
        '409':
          description: The action was already decided or executed.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Approves the action, once
      tags:
        - actions
  /projects/{projectId}/sessions/{sessionId}/actions/{actionId}/approve_always:
    post:
      description: >-
        Besides releasing this action, it adds the corresponding pattern to the
        project's `allow` list — future matching actions come out
        `auto_approved` without asking. A pattern already in `deny` stays
        blocked. The approval and the pattern are recorded in the same
        transaction. Clicking an action that was already APPROVED is idempotent
        success (`desfecho: ja_aprovada`), recording the pattern only if it is
        missing.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or action not found.
        '409':
          description: >-
            The action was already DENIED (`reason: acao_ja_recusada`); no
            pattern is recorded.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Approves the action and records the pattern in permissions.json
      tags:
        - actions
  /projects/{projectId}/sessions/{sessionId}/actions/{actionId}/deny:
    post:
      description: The reason, if given, stays in the event log alongside the decision.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or action not found.
        '409':
          description: The action was already decided or executed.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Denies the action
      tags:
        - actions
  /projects/{projectId}/sessions/{sessionId}/agents/{agent}/cancel:
    post:
      description: >-
        The composer's "Stop" button (RN-122): kills the in-flight LLM call in
        the engine, cutting the connection mid-stream — it saves tokens for
        real, not just stops rendering on the client. Idempotent: with no turn
        in progress, it is accepted with no effect.
      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 of the active agent.
          schema:
            example: po
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OkResponseDto'
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or handoff not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Cancels the active agent's turn in progress
      tags:
        - agents
  /projects/{projectId}/sessions/{sessionId}/agents/{agent}/message:
    post:
      description: >-
        The response is just the acknowledgment, and it returns on ACCEPTANCE —
        before the agent's turn ends (ADR 0163). What the agent replies arrives
        via the session's event log and channel — not through this call. If the
        agent is mid-turn, the message is NOT refused: it joins the agent's
        queue (`entrega: "enfileirada"`) and is read, with any others queued, in
        one turn when the current one ends; it can be cancelled while it waits
        (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 of the conversational agent that reads the message: criativo,
            po, arquiteto, dev-lead, ux-designer or staff. Any other slug —
            infra included — is refused with 422; it is never delivered to a
            default agent (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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or handoff not found.
        '409':
          description: >-
            The agent is not active in this session; or it is waiting on an
            execution-plan decision; or its queue already holds 10 messages
            (`fila_de_mensagens_cheia`) — the message was recorded but NOT read
            by the agent (ADR 0163, RN-673).
        '422':
          description: >-
            The agent does not take chat messages (the Infra Lead works by
            proposal, and any slug without its own clause in the engine is
            refused by name) — the message was recorded but NO agent read it
            (RN-584).
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Sends a message to the active agent
      tags:
        - agents
  /projects/{projectId}/sessions/{sessionId}/agents/{agent}/messages/{messageId}/cancel:
    post:
      description: >-
        A message sent while the agent is mid-turn waits in a queue and is read
        when the turn ends (RN-673). While it waits, the person who SENT it can
        cancel it: the engine removes it from the queue and records
        `chat.message_cancelled`; it is never shown to the model. Someone else's
        message is 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 of the agent whose queue holds the message.
          schema:
            example: po
            type: string
        - name: messageId
          required: true
          in: path
          description: >-
            Id of the `chat.message` event (the `mensagemId` the message route
            returned).
          schema:
            example: 01JC4Z0000EVENTO000000000001
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OkResponseDto'
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or handoff not found.
        '409':
          description: >-
            The message is no longer queued — the agent already read it, or it
            was already cancelled; or the session is closed (RN-581).
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Cancels one message waiting in the agent's queue
      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: No token, expired token, or invalid signature.
        '403':
          description: Role below `developer` on the project.
        '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 per user or per IP.
      security:
        - bearer: []
      summary: Resumes the turn an engine restart interrupted
      tags:
        - sessions
  /projects/{projectId}/sessions/{sessionId}/agents/{agent}/start:
    post:
      description: >-
        Only the Criativo can be started by a direct command. Every other agent
        requires an ACCEPTED handoff pointing to it — the rule lives in the
        domain, not here, and trying to bypass it returns 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: Agent slug.
          schema:
            example: criativo
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgenteAtivadoResponseDto'
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or handoff not found.
        '409':
          description: Agent without an accepted handoff, or already active in the session.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Starts an agent in the session
      tags:
        - agents
  /projects/{projectId}/sessions/{sessionId}/agents/{agent}/structured-question/{questionSetId}/answer:
    post:
      description: >-
        Records `chat.structured_question_answered` and resends the answers to
        the agent as a normal message. A question set can only be answered once.
        Returns on ACCEPTANCE, before the agent's turn ends (ADR 0163): the turn
        keeps running in the engine and its narration, end and failures arrive
        through the session channel and the event log (`agent.status`,
        `agent.response`, `agent.error`) — never through this response.
      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 of the agent that asked the questions (and will read the
            answer).
          schema:
            example: criativo
            type: string
        - name: questionSetId
          required: true
          in: path
          description: Id of the answered `chat.structured_question` event.
          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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or handoff not found.
        '409':
          description: >-
            This question set has already been answered; or the agent is still
            in the middle of a turn and did not read the answers (ADR 0163).
        '422':
          description: >-
            The agent that asked does not take chat messages — the answers were
            recorded but NO agent read them (RN-584).
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Answers a set of the agent's structured questions
      tags:
        - agents
  /projects/{projectId}/sessions/{sessionId}/agents/{agentId}/rearm:
    post:
      description: >-
        Resets the consecutive blocked-tasks counter (RN-047) and puts the agent
        back to trying to claim work. It is the only way out of `idle_tripped` —
        there is no automatic unblock.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or task not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Rearms a dev agent tripped by the circuit breaker
      tags:
        - execution
  /projects/{projectId}/sessions/{sessionId}/agents/arquiteto/handoff-infra:
    post:
      description: >-
        Dedicated endpoint instead of reusing `readiness`, which belongs to the
        Criativo: they are two different milestones of the session, and
        conflating them would make the event log ambiguous. Returns on
        ACCEPTANCE, before the agent's turn ends (ADR 0163): the turn keeps
        running in the engine and its narration, end and failures arrive through
        the session channel and the event log (`agent.status`, `agent.response`,
        `agent.error`) — never through this response. The Dev Lead handoff still
        comes AFTER the Infra one: the engine holds it until the closing turn
        ends. Idempotent (ADR 0182, RN-635): a target that already has a pending
        offer or is active in the project is not triggered again, and with both
        like that nothing is recorded.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or handoff not found.
        '409':
          description: >-
            The Arquiteto is still in the middle of a turn — the confirmation
            was recorded but the closing turn did not start (ADR 0163).
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Confirms the architecture is ready and offers the handoff to Infra
      tags:
        - agents
  /projects/{projectId}/sessions/{sessionId}/agents/criativo/validate-necessity:
    post:
      description: >-
        Records `necessity.validated`. Requires the Criativo to have already
        consolidated a `product_brief` in this session (RN-406) — without it, it
        is refused: there is nothing to validate. Since ADR 0185 (RN-657) the
        "I'm ready — the need is validated" click (`POST .../readiness`) records
        this event itself, and the web no longer calls this route; it stays for
        sessions whose readiness click predates that 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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or handoff not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Confirms the Criativo's business need has been validated
      tags:
        - agents
  /projects/{projectId}/sessions/{sessionId}/budget:
    get:
      description: This is what feeds the session screen's live token meter.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project or session not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Reads the session budget and the accumulated spend
      tags:
        - llm
    put:
      description: Same dollar-to-micro-USD conversion as the project budget.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project or session not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Sets the session spend cap
      tags:
        - llm
  /projects/{projectId}/sessions/{sessionId}/chat:
    post:
      description: >-
        Server-Sent Events. `delta` frames carry the incremental text and `done`
        closes with the token and cost accounting. A `metering_failed` frame
        means the RESPONSE went out but the cost was not accounted for — the
        failure shows up instead of disappearing. If the budget is exceeded with
        `policy=block`, the stream carries `error` and no `delta`. This route
        carries NO agent: it sends the text alone to the bound model, with no
        history and no system prompt. In a `consultiva` session where no agent
        was ever activated it is refused before any effect (RN-682) — talk to an
        agent through `.../agents/:agent/message` instead.
      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: Stream of frames until `done` or `error`.
          content:
            text/event-stream:
              schema:
                $ref: '#/components/schemas/ChatSseEventResponseDto'
        '400':
          description: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project or session not found.
        '422':
          description: >-
            `destinatario_ausente`: a `consultiva` session with no agent — the
            message has no recipient. Nothing was recorded and no model was
            called (RN-682).
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Talks to the session's model, with the response streamed
      tags:
        - llm
  /projects/{projectId}/sessions/{sessionId}/context-summary:
    get:
      description: >-
        Business rules, decision records and artifacts per agent counted over
        the WHOLE session log (the screen reads a 200-event tail), plus the
        files touched by `git_commit`/`git_push` in this session and in the
        execution session its approved Dev Lead plan activated (RN-828). Since
        RN-844 it also returns the ITEMS of rules and decisions (latest 50 each;
        the totals stay in `regras`/`decisoes`) and the files of the PRs opened
        in those sessions, read from the same diff the PRs tab reads, for at
        most 20 PRs per call; `prsNaoLidas` says how many were left out or
        failed. Read only; no new table.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role in the project.
        '404':
          description: Project or session does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Counts the whole session for the context panel
      tags:
        - sessions
  /projects/{projectId}/sessions/{sessionId}/events:
    get:
      description: >-
        The log is IMMUTABLE and ordered by `seq`. Normal pagination is
        incremental: pass the previous response's `nextCursor` as `afterSeq`.
        `latest=true` does the opposite — it fetches the TAIL and ignores
        `afterSeq`, for a screen that opens at the end instead of reconstructing
        the whole session from the start (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: Returns the events with `seq` greater than this.
          schema:
            example: 40
            type: string
        - name: limit
          required: false
          in: query
          schema:
            example: 50
            type: string
        - name: latest
          required: false
          in: query
          description: Fetches the tail of the log; ignores `afterSeq`.
          schema:
            example: 'true'
            type: string
        - name: actionId
          required: false
          in: query
          description: >-
            Only the events whose `payload.actionId` is this action — e.g. its
            `proposed_action.created`, which carries the policy `reason` and
            `scopeRoot` the action row does not keep (RN-614). Combines with the
            other parameters; `seq` is no longer contiguous in the page, so a
            caller filtering by action must not derive omitted counts from it.
            An empty value is a `400`.
          schema:
            example: 01JC4Z0000ACAO00000000000001
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaginaDeEventosResponseDto'
        '400':
          description: '`actionId` is present but empty.'
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role in the project.
        '404':
          description: Project or session does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Paginates the session's event log
      tags:
        - sessions
    post:
      description: >-
        Append-only: `seq` is assigned by the server and nothing in the log is
        updated or removed afterward. There is no edit endpoint, by design.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role in the project.
        '404':
          description: Project or session does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Appends an event to the session's log
      tags:
        - sessions
  /projects/{projectId}/sessions/{sessionId}/events/{eventId}:
    get:
      description: >-
        Exists so the Psychologist's hypothesis evidence can reach the cited
        event regardless of which page it fell on or the feed's filters.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role in the project.
        '404':
          description: Project or session does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Returns a log event by id
      tags:
        - sessions
  /projects/{projectId}/sessions/{sessionId}/execution/parallelize:
    post:
      description: >-
        Goes through the dev area CAP (RN-083): under it, the agent starts right
        away, in its own worktree; above it, the response is a `proposed_action`
        of type `parallelize` awaiting your decision, and NOTHING starts until
        you decide. What the cap is set to is configurable per lead in Settings.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or task not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Requests one more dev agent for a module
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or handoff not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Lists the session's agent-to-agent handoffs
      tags:
        - agents
    post:
      description: >-
        Born as `offered`, exactly like an agent's own `offer_handoff` — the
        only difference is who decided. `toAgent` has to be in the addressable
        catalog (area lead or area-less agent); a subagent or an unknown slug is
        refused with 400. At most one pending offer per (project, target): see
        `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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or handoff not found.
        '409':
          description: >-
            `agente_ja_ativo` (ADR 0182, RN-635): the target is already active
            in a non-closed session of the project — no offer is created.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Manually offers a handoff to a chosen agent
      tags:
        - agents
  /projects/{projectId}/sessions/{sessionId}/handoffs/{handoffId}/accept:
    post:
      description: >-
        One thing only: transition to `accepted` AND start the destination
        agent. It is the normal activation path for every agent other than the
        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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or handoff not found.
        '409':
          description: The handoff is not in `offered`.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Accepts a handoff and activates the destination agent
      tags:
        - agents
  /projects/{projectId}/sessions/{sessionId}/model-binding:
    get:
      description: >-
        Returns the binding RESOLVED by the cascade, not the session's raw
        binding: a session with no binding of its own uses the project's, and
        answering `null` here would be the wrong answer to the right question.
        The `origin` field says which scope the value came from.


        `agentId` is optional and is the agent REALLY active in the session
        right now (the same one `RunLlmTurnUseCase` uses to run the turn) —
        without it, the cascade only sees session→project→workspace (plus the
        fixed fallback to Creative) and never reflects PO/Architect/Dev
        Lead/area after a 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: >-
            Agent currently active in this session (e.g. "po", "arquiteto",
            "dev-lead").
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResolvedBindingResponseDto'
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the scope.
        '404':
          description: Scope not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Resolves which model the session uses, and where it came from
      tags:
        - llm
    put:
      description: Beats everything else in the cascade for as long as the session lives.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the scope.
        '404':
          description: Scope not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Pins the model for this session
      tags:
        - llm
  /projects/{projectId}/sessions/{sessionId}/psychologist/reanalyze:
    post:
      description: >-
        Requires `maintainer`, not `developer`, because it runs the ToolLoop
        again and SPENDS real budget — unlike accepting or dismissing, which
        only move local state. The previous analysis is marked superseded.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or hypothesis does not exist.
        '429':
          description: Rate limit per user or per IP.
        '503':
          description: >-
            The Psychologist is disabled globally by the user's decision (not a
            bug) — body with `reason: "psychologist_disabled"`.
      security:
        - bearer: []
      summary: Triggers a reanalysis of the session
      tags:
        - psychologist
  /projects/{projectId}/sessions/{sessionId}/readiness:
    post:
      description: >-
        It's the button that triggers the `product_brief` and the handoff to the
        PO. Records `readiness.confirmed` in the event log. Returns on
        ACCEPTANCE, before the agent's turn ends (ADR 0163): the turn keeps
        running in the engine and its narration, end and failures arrive through
        the session channel and the event log (`agent.status`, `agent.response`,
        `agent.error`) — never through this response.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or handoff not found.
        '409':
          description: >-
            The Criativo is still in the middle of a turn — the confirmation was
            recorded but the brief did not start (ADR 0163).
        '422':
          description: >-
            No business rule was captured in this conversation — there is
            nothing to consolidate into a brief yet (ADR 0163).
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Confirms that the discovery session with the Criativo is done
      tags:
        - agents
  /projects/{projectId}/sessions/{sessionId}/reopen:
    post:
      description: >-
        Only `closed`/`closed_abnormally` reopen, and only to `active` —
        `closing` never goes back (ADR 0183). The session keeps its event log,
        artifacts, answered questions and handoffs; `kind` is untouched. A NEW
        event `session.reopened` records the previous `closedAt` and
        `terminationReason`, which the row clears. A session that carries
        `execution.activated` is refused (`sessao_com_execucao`): open a new
        session and activate execution there. No time limit. Requires
        `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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role in the project.
        '404':
          description: Project or session does not exist.
        '409':
          description: >-
            Session is not closed (`reason: sessao_nao_encerrada`), or it
            activated execution (`reason: sessao_com_execucao`).
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Reopens a closed session, keeping everything it had
      tags:
        - sessions
  /projects/{projectId}/sessions/{sessionId}/response-language:
    get:
      description: >-
        Resolved for the CALLER, never for the session: session override >
        Account choice > detected-and-confirmed > interface language (RN-618).
        Returns the winner, its origin and every link of the chain — and, when
        your recent messages point to another language and the screen should ask
        about it, `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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role in the project.
        '404':
          description: Project or session does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: The language agents answer YOU in, in this session
      tags:
        - sessions
    put:
      description: >-
        Only for the caller, only in this session (RN-618) — the other
        participants and your Account choice are untouched. `null` goes back to
        inheriting from the Account.
      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: >-
            Body without `language`, or a code that is not a recognized BCP-47
            language.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role in the project.
        '404':
          description: Project or session does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Fixes (or releases) YOUR response language in this session
      tags:
        - sessions
  /projects/{projectId}/sessions/{sessionId}/socket-ticket:
    post:
      description: >-
        The ticket authenticates the session's Phoenix socket `session:<id>`
        `connect/3` on the engine — it is NOT the reused JWT. 30s TTL and single
        use: every reconnection (including automatic ones) requests a new
        ticket. `scope: "heartbeat"` requires the `viewer` role; `scope:
        "terminal"` requires `developer`, the same minimum role as terminal
        actions.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role for the requested scope.
        '404':
          description: Project or session does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Issues an opaque, single-use ticket for the session's socket
      tags:
        - sessions
  /projects/{projectId}/sessions/{sessionId}/tasks/{taskId}/unblock:
    post:
      description: >-
        Resets the gate correction counter and hands the task back to the agent.
        It is the human escape hatch for the correction cap — there is no
        automatic unblock.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project, session, or task not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Unblocks a blocked task
      tags:
        - execution
  /projects/{projectId}/sessions/{sessionId}/token-usage:
    get:
      description: >-
        The data was always in `token_usage`, but without aggregation or a route
        the team panel had no way to show the cost per card. Same role required
        as the session budget.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project or session not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Breaks down the session spend by agent
      tags:
        - llm
  /projects/{projectId}/sessions/{sessionId}/transition:
    post:
      description: >-
        The transition is validated by the domain's state machine, not by the
        client: created → active → closing → closed | closed_abnormally. An
        invalid jump is 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: >-
            Returns 201, not 200, because the handler is `@Post` without
            `@HttpCode` — that's Nest's default. Documented as-is instead of
            changed: the semantics are debatable, the behavior isn't defective,
            and the table test fails on any divergence between the two.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionResponseDto'
        '400':
          description: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role in the project.
        '404':
          description: Project or session does not exist.
        '409':
          description: Transition not allowed from the current state.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Moves the session to another state
      tags:
        - sessions
  /projects/{projectId}/spend/me:
    get:
      description: >-
        The MEMBER's audience. Only the caller's own rows — the actor comes from
        the authenticated token and there is no parameter to swap it. In tokens
        and ESTIMATED cost, without breaking down by provider or by credential:
        the key that ran is the owner's (RN-058) and its invoice is theirs
        (RN-060). The provider axis came to exist in the owner's report (ADR
        0076) and remains unreachable from here — there is no dimension
        parameter on this route, and the repository type refuses the combination
        (RN-187). Agent doesn't count: `token_usage` records who spent, not who
        ordered the spend.
      operationId: SpendController_getMySpend
      parameters:
        - name: projectId
          required: true
          in: path
          schema:
            type: string
        - name: dias
          required: false
          in: query
          description: Sliding window. Default 30, max 180.
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MySpendResponseDto'
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: My own consumption in this project, by session and by day
      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: Sliding window. Default 30, max 180.
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectSpendTeamResponseDto'
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: The project's total spend and cost by agent and area
      tags:
        - llm
  /projects/{projectId}/stories/{storyId}/archive:
    post:
      description: >-
        The story leaves the backlog, the coverage, the promotion queue, the Dev
        Lead's plan and the claim — and so do its tasks. Nothing is deleted: the
        row stays with `archivedAt`, and `backlog.story_archived` records who,
        why and which tasks. Only a `draft` story with no task in execution.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project does not exist.
        '409':
          description: >-
            Named refusal (`reason`): `historia_arquivada`, `historia_nao_draft`
            or `historia_com_tarefa_em_execucao`.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Archives a draft story (RN-727)
      tags:
        - backlog
  /projects/{projectId}/stories/{storyId}/return:
    post:
      description: >-
        The story leaves the proposal queue and the reason becomes a message
        pinned to the PO's session — the same pattern as returning a gate to
        dev. If that session's PO is no longer up, the return is recorded
        anyway: it's the user's decision and does not depend on an agent
        listening.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Returns a refused story to the PO, with the reason
      tags:
        - backlog
  /projects/{projectId}/stories/{storyId}/title:
    patch:
      description: >-
        Only a `draft` story with no task in execution
        (`in_progress`/`in_review`). Writes `backlog.story_updated` with the
        previous and the new title. Same role as the other backlog writes
        (promote/return).
      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: Empty title or over 200 characters.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project does not exist.
        '409':
          description: >-
            Named refusal (`reason`): `historia_arquivada`, `historia_nao_draft`
            or `historia_com_tarefa_em_execucao`.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Corrects the title of a draft story (RN-727)
      tags:
        - backlog
  /projects/{projectId}/stories/promote:
    post:
      description: >-
        The human step that `manual` mode (default since Phase 12c) hands back
        to the user: until promotion, NO task from the story is pickable by any
        dev agent. Promoting releases the batch of tasks at once and wakes up
        the idle agents of the module (Phase 12b). The batch is NOT
        all-or-nothing — whatever didn't pass comes back as `failed` with the
        reason, and whatever passed is promoted.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project does not exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Promotes stories the PO proposed to `ready`
      tags:
        - backlog
  /projects/{projectId}/tasks/{taskId}/gates/{gate}/resume:
    post:
      description: >-
        A gate cycle stalled for more than 2 h is parked instead of restarted on
        its own (ADR 0207). This is the human gesture that resumes it.
      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: No token, expired token, or invalid signature.
        '403':
          description: Role below `developer` on the project.
        '409':
          description: >-
            The cycle is not parked — body with `reason:
            "gate_nao_estacionado"`.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Resumes a parked gate cycle
      tags:
        - gates
  /projects/{projectId}/terminal-ticket:
    post:
      description: >-
        Same `/runner` socket, same topic, role "terminal" — whoever SEES the
        terminal (the command itself only really runs if a runner is connected).
        Valid for any project mode.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the project.
        '404':
          description: Project not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Issues a single-use ticket for the web Terminal tab
      tags:
        - projects
  /runner-releases/binary:
    get:
      description: >-
        GitHub Releases proxy — `platform` accepts only linux-x64, linux-arm64,
        darwin-arm64. No authentication: the binary is not a secret. The bytes
        are checked against the `checksums.txt` of the same Release before
        anything is answered (ADR 0149, RN-525); the route does NOT verify the
        manifest signature — that is integrity, not provenance, and whoever
        verifies the signature is `install.sh`.
      operationId: RunnerReleasesController_binary
      parameters: []
      responses:
        '200':
          description: >-
            The runner binary, streamed, with the sha256 already checked against
            the manifest. Not JSON: `Content-Type: application/octet-stream`.
            The `Content-Digest` header repeats the checked hash (RFC 9530).
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        '502':
          description: >-
            Named refusal — never bytes with a warning. The body carries
            `motivo`: `plataforma_nao_publicada`, `release_sem_manifesto`,
            `manifesto_nao_cobre_a_plataforma`, `manifesto_ilegivel`,
            `download_falhou` or `hash_divergente`.
      summary: Downloads the standalone local runner binary for the requested platform
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Credential bound to a project.
        '409':
          description: Code `credencial_existente`.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Stores an LLM API key imported by the local runner
      tags:
        - credentials
  /runner/projects:
    get:
      description: >-
        Authenticated by a MACHINE device key (ADR 0154) — NEVER by a session
        JWT, and never by a credential bound to a project, which answers 403.
        Returns the projects in `execution_mode: "runner"` in which the key
        owner reaches at least `developer` (the same minimum as `POST
        .../runner-ticket`), with the folder name and verification state of
        each. The agent asks instead of scanning the disk: the machine base
        belongs to the user and may hold folders that are not projects.
      operationId: RunnerProjectsController_listRunnerProjects
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/RunnerProjectResponseDto'
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: >-
            The presented credential is bound to a project — this route requires
            a machine device key.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Lists the projects in "runner" mode that this local agent serves
      tags:
        - projects
  /users/me/credentials:
    get:
      description: >-
        No role required: a credential is about whoever called, not about a
        workspace or project. Covers LLM keys and git tokens in the same
        listing.
      operationId: CredentialsController_list
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/UserCredentialResponseDto'
        '401':
          description: No token, expired token, or invalid signature.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Lists the current user's own credentials
      tags:
        - credentials
    post:
      description: >-
        Upsert by (user, provider). The key is encrypted via envelope encryption
        before touching the database, and the response carries only the
        PROJECTION — never the secret, not even encrypted.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Registers or replaces a provider's API key
      tags:
        - credentials
  /users/me/credentials/{provider}:
    delete:
      description: 'Idempotent: deleting what does not exist also responds `{ 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: No token, expired token, or invalid signature.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Removes a provider's credential
      tags:
        - credentials
  /users/me/credentials/{provider}/test:
    post:
      description: >-
        The check is an explicit action, separate from registration (ADR 0050):
        the api decrypts the secret, calls the provider, and returns ONLY the
        verdict — the key never comes back, not even in part. Responds 200 for
        all three outcomes, because the request was processed; `refused` is an
        outcome, not a protocol error. 404 when there is no credential for
        (user, provider) — then there is nothing to test.
      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: No token, expired token, or invalid signature.
        '404':
          description: No credential for this provider.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Checks the ALREADY stored credential against the provider
      tags:
        - credentials
  /users/me/git-credentials:
    post:
      description: >-
        Its own path, not the `users/me/credentials` one, because the body is
        different (`token`, and the provider enum is `github`/`gitlab`). The
        token is encrypted and stored without prior verification (ADR 0050); to
        verify it, `POST /users/me/credentials/{provider}/test`. Reading,
        removal, and testing reuse the `users/me/credentials` routes — it's the
        same table.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Registers a git token (encrypted, without testing)
      tags:
        - credentials
  /users/me/machine-device-keys:
    get:
      description: >-
        Account-level, with no project in the path (RN-611): the one-line
        install creates a machine key before any project exists, and every other
        listing is per project. Returns ONLY machine keys (`especie: "maquina"`,
        `projectId: null`) and ONLY the caller’s — project keys stay in their
        project’s listing, and no one sees another user’s keys (RN-519). Revoked
        keys are INCLUDED: registering a new machine key revokes the previous
        one (RN-552), and this list is where that shows. `lastUsedAt` is a
        recorded use, never a live connection; null means the key was never
        used.
      operationId: MachineDeviceKeysController_listMachineDeviceKeys
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/RunnerDeviceKeyListResponseDto'
        '401':
          description: No token, expired token, or invalid signature.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Lists the authenticated user's own MACHINE device keys
      tags:
        - users
  /users/me/machine-device-keys/{deviceKeyId}:
    delete:
      description: >-
        Idempotent — revoking again is not an error. Same revocation as `DELETE
        /projects/{projectId}/runner-device-keys/{deviceKeyId}`: it also drops
        the LIVE connections opened with this key, in EVERY project
        (RN-520/RN-543). Since RN-685 (ADR 0201) the target is the KEY: another
        runner of the same user, connected with a PAT or with another key, stays
        up. The key’s still-unused tickets are voided. With no project yet there
        is no connection to drop. A PROJECT key, a key that does not exist and
        another user’s key all answer the same 404.
      operationId: MachineDeviceKeysController_revokeMachineDeviceKey
      parameters:
        - name: deviceKeyId
          required: true
          in: path
          schema:
            type: string
      responses:
        '204':
          description: Key revoked. No body.
        '401':
          description: No token, expired token, or invalid signature.
        '404':
          description: >-
            No machine key with this id belongs to the caller (it does not
            exist, is a project key, or is someone else’s — one answer for all).
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Revokes one of the authenticated user’s own MACHINE device keys
      tags:
        - users
  /users/me/preferences:
    get:
      operationId: UserPreferencesController_get
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserPreferencesResponseDto'
        '401':
          description: No token, expired token, or invalid signature.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: >-
        Reads the authenticated user's interface language and agent response
        language
      tags:
        - users
    patch:
      description: >-
        Both fields are optional and independent — sending one never touches the
        other, and a body with neither is a 400. `locale` (the interface) stays
        closed to the `pt-BR`/`en` list. `responseLanguage` (RN-618) is
        `automatico` or any recognized BCP-47 code, stored canonical; an
        unrecognized code is a 400 and nothing in the body is written.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: >-
        Writes the authenticated user's interface language and/or agent response
        language
      tags:
        - users
  /users/me/preferences/detected-language:
    post:
      description: >-
        The only way a detected language becomes a preference (RN-624):
        detection alone never changes anything. `confirm` writes
        `detectedLanguage` + `detectedLanguageConfirmedAt`; `decline` records
        the refusal so the same language is not asked again. Returns the
        preferences after the answer.
      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: A code that is not a recognized BCP-47 language.
        '401':
          description: No token, expired token, or invalid signature.
        '409':
          description: >-
            `deteccao_mudou`: on `confirm`, your recent messages no longer point
            to that language — nothing was written.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Answers the detected-language question
      tags:
        - users
  /workspaces:
    get:
      description: >-
        Already filtered by association — there is no global listing. Each item
        carries the caller's own role.
      operationId: WorkspacesController_list
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/WorkspaceComPapelResponseDto'
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role in the workspace.
        '404':
          description: Workspace doesn't exist or is invisible to the caller.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Lists the caller's workspaces
      tags:
        - workspaces
    post:
      description: >-
        Requires no role: whoever creates it becomes `owner`. It's the only
        entry point for someone who doesn't belong to any workspace yet.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role in the workspace.
        '404':
          description: Workspace doesn't exist or is invisible to the caller.
        '409':
          description: A workspace with this slug already exists.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Creates a 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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role in the workspace.
        '404':
          description: Workspace doesn't exist or is invisible to the caller.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Returns a workspace by 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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role in the workspace.
        '404':
          description: Workspace doesn't exist or is invisible to the caller.
        '409':
          description: A workspace with this slug already exists.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Changes the workspace's name or slug
      tags:
        - workspaces
    delete:
      description: >-
        Requires `owner`. Takes its projects, sessions, and their history along
        with it.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role in the workspace.
        '404':
          description: Workspace doesn't exist or is invisible to the caller.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Removes the workspace
      tags:
        - workspaces
  /workspaces/{workspaceId}/containers:
    get:
      description: >-
        One row per project of the workspace — since RN-521 a project that never
        provisioned a container is PRESENT with `registrado: null` instead of
        absent, because this page is the human path to start the first one. The
        observed state is asked of the broker only for rows
        `provisioning`/`running`, and only up to a per-load budget — see
        `naoVerificado` on rows that were skipped, and ADR 0136 for the
        reasoning.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role in the workspace.
        '404':
          description: Workspace doesn't exist or is invisible to the caller.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Lists every project in the workspace and its container, if any
      tags:
        - projects
  /workspaces/{workspaceId}/credential-spend:
    get:
      description: >-
        Exists because the key agents spend is the workspace OWNER's (RN-058) —
        whoever pays the bill needs to see the bill. Groups by provider because
        that is the unit of the credential, and separates what went out by AGENT
        from what went out by a person in chat: the two come out of the same key
        and answer different questions. Returns no secret at all.
      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: No token, expired token, or invalid signature.
        '403':
          description: Requires `owner` on the workspace.
        '404':
          description: Project or session not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: How much the owner's keys spent, by provider and by month
      tags:
        - llm
  /workspaces/{workspaceId}/huggingface/models:
    get:
      description: >-
        Filters to OFFICIAL publishers only by default (the allowlist in
        `domain/llm/huggingface-official-publishers.ts`) — same "manual curation
        always" spirit as the LLM catalog (ADR 0042), applied to the "official"
        badge instead of to activation. `includeCommunity=true` brings in every
        publisher, each one tagged `official: false` for the screen to render
        the security warning — never hidden.
      operationId: HuggingFaceModelsController_search
      parameters:
        - name: q
          required: true
          in: query
          description: Search text.
          schema:
            type: string
        - name: includeCommunity
          required: false
          in: query
          description: Pass `true` to also include non-official publishers.
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/HuggingFaceModelResponseDto'
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Requires `maintainer` on the workspace.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Searches GGUF models on the Hugging Face Hub
      tags:
        - llm
  /workspaces/{workspaceId}/huggingface/pull-requests:
    post:
      description: >-
        First of two explicit steps: this call only records the intent, in
        `pending_confirmation`. Nothing is pulled until a separate call to `POST
        .../pull-requests/:id/confirm` — the product never runs an automatic,
        silent pull.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Requires `maintainer` on the workspace.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Creates a PENDING pull request — nothing downloads yet
      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: No token, expired token, or invalid signature.
        '403':
          description: Requires `maintainer` on the workspace.
        '404':
          description: Pull request not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Status of a pull request, for the frontend to poll
      tags:
        - llm
  /workspaces/{workspaceId}/huggingface/pull-requests/{id}/confirm:
    post:
      description: >-
        Moves `pending_confirmation` → `confirmed` → `pulling`, calls the Ollama
        daemon, and on success upserts the model into the catalog (`models`) and
        activates it for THIS workspace. On failure the request ends in `failed`
        with a reason declaring its origin (infra | model | code | policy) —
        never a silent failure. This call runs SYNCHRONOUSLY for the whole pull
        (see the use case for why); use the GET route below to poll instead of
        holding this connection open.
      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: No token, expired token, or invalid signature.
        '403':
          description: Requires `maintainer` on the workspace.
        '404':
          description: Pull request not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: The second confirmation — actually runs the pull
      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: No token, expired token, or invalid signature.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Lists the LLM providers the workspace owner has a key for
      tags:
        - llm
  /workspaces/{workspaceId}/members:
    get:
      description: >-
        Who is associated with the WORKSPACE, with the workspace role — the role
        every project inherits unless a project row overrides it. Completes `GET
        projects/:projectId/members`, which lists only project rows: whoever
        reaches a project through the workspace alone shows up here and not
        there. Only id, name, e-mail and role.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role in the workspace.
        '404':
          description: Workspace doesn't exist or is invisible to the caller.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Lists the workspace's members
      tags:
        - workspaces
    post:
      description: >-
        Only `owner` can touch the member roster. The role here is inherited by
        ALL of the workspace's projects. That role is NECESSARY but not
        SUFFICIENT: changing YOUR OWN role here is refused with 403 in both
        directions, and cannot be enabled anywhere. There is no level above to
        catch the fall, so a self downgrade would be unrecoverable through the
        UI. Demoting ANOTHER `owner` is still allowed — it is the only way
        ownership is revoked.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: >-
            Not `owner` of the workspace, OR the target is the caller and the
            requested role differs from their current one (self-movement cap).
        '404':
          description: Workspace doesn't exist or is invisible to the caller.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Associates a user with the workspace
      tags:
        - workspaces
  /workspaces/{workspaceId}/members/{userId}:
    delete:
      description: >-
        Only `owner`, like the upsert. Removes the workspace row AND, in the
        same transaction, the user's rows in every project of this workspace
        (the project role overrides the workspace one, so leaving them would
        keep the user inside those projects), plus the user's PROJECT device
        keys and personal access tokens in them. Afterwards the live runner
        connection of that user in each project is dropped, best effort. Machine
        device keys and open sessions are left alone — they stop reaching this
        workspace because the role resolves to none.


        Removing YOURSELF is refused with 403 and cannot be enabled anywhere:
        there is no level above to catch the fall. That same clause is what
        protects the last `owner` — only an `owner` can call this route and
        nobody removes themselves, so every successful call leaves at least the
        caller as `owner`. Removing ANOTHER `owner` is allowed — it is how
        ownership is revoked — except the OWNER OF RECORD (`createdBy`), refused
        with 409 `criador_do_workspace` until `PUT :workspaceId/owner-of-record`
        moves it to another owner: the LLM key agents spend and the spend report
        are theirs. Idempotent: a target without a workspace row still has this
        workspace's project rows cleared, and gets `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: Association removed. No body.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: >-
            Not `owner` of the workspace, OR the target is the caller
            (self-removal cap).
        '404':
          description: Workspace doesn't exist or is invisible to the caller.
        '409':
          description: >-
            `criador_do_workspace`: the target is the owner of record; transfer
            it first.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Disassociates a user from the workspace
      tags:
        - workspaces
  /workspaces/{workspaceId}/model-binding:
    get:
      description: This scope's RAW binding. It is the root of the cascade.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the scope.
        '404':
          description: Scope not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Reads the workspace model binding
      tags:
        - llm
    put:
      description: Applies to every project that has no binding of its own.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the scope.
        '404':
          description: Scope not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Sets the default model for the 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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Requires `owner` on the workspace.
        '404':
          description: Model not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Sets a model's curated billing mark (api | subscription | free)
      tags:
        - llm
  /workspaces/{workspaceId}/models/{modelId}/price-changes:
    get:
      description: >-
        Most recent first. It is an immutable log: never gets an UPDATE, and the
        row carries the before/after pair so the audit does not depend on
        reconstructing the "before" from the previous row.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the workspace.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: History of a model's price changes
      tags:
        - llm
  /workspaces/{workspaceId}/models/{modelId}/pricing:
    patch:
      description: >-
        Applies from now on and NEVER re-prices the past: each `token_usage` row
        keeps the price that produced its cost. The change stays recorded with
        the before/after pair — queryable on the history route.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Requires `owner` on the workspace.
        '404':
          description: Model not found.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Corrects a model's price
      tags:
        - llm
  /workspaces/{workspaceId}/models/activate:
    post:
      description: >-
        Owner's curation, APPLYING ONLY TO THIS WORKSPACE (ADR 0049). Only
        touches `isActive` — `availability` is what the sync observed at the
        provider, is global, and does not change here. Whole batch or nothing: a
        nonexistent id fails the call without applying any row.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Requires `owner` on the workspace.
        '404':
          description: Some id in the batch doesn't exist.
        '422':
          description: >-
            Activating an OpenRouter free-routing alias (`~…`) — `code:
            "alias_de_roteamento_livre"`, with the `modelIds` refused. The whole
            batch is refused. Deactivating one is always allowed (AT-271,
            RN-679).
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Turns models on or off in the selector (batch)
      tags:
        - llm
  /workspaces/{workspaceId}/models/catalog:
    get:
      description: >-
        The curation screen. A model discovered by the sync comes in as
        `isActive: false` and only appears here until someone activates it; a
        model that disappeared from the provider comes with `availability:
        "unavailable"` and is never deleted, because bindings and cost history
        point to it. `isActive` is FOR THIS workspace (ADR 0049); `availability`
        is global, because it is what the sync observed at the 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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role on the workspace.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Lists the WHOLE catalog, including inactive and unavailable
      tags:
        - llm
  /workspaces/{workspaceId}/models/sync:
    post:
      description: >-
        Same use case as the internal route the engine schedules — the curation
        screen's refresh button has no path of its own, so there are never two
        reconciliations that could diverge.
      operationId: ModelsController_sync
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SyncModelCatalogResponseDto'
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Requires `owner` on the workspace.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Forces the catalog sync the periodic job does, right now
      tags:
        - llm
  /workspaces/{workspaceId}/models/uses:
    post:
      description: >-
        The axis no catalog publishes: "good for code" is not a capability
        declared by any provider, it is the operator's opinion — and applies
        ONLY to this workspace (ADR 0049). Axis independent of activation:
        marking a use does not turn the model on in the selector, and changing
        the use does not turn off what was on. The uses list REPLACES the
        previous one.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Requires `owner` on the workspace.
        '404':
          description: Some id in the batch doesn't exist.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Marks what the workspace uses each model for (batch)
      tags:
        - llm
  /workspaces/{workspaceId}/owner-of-record:
    put:
      description: >-
        The owner of record is `createdBy`: whose LLM credential the agents
        spend and whose git credential agent actions use (RN-058), and who owns
        the spend report (RN-060). It does not grant authorization — roles do.
        The target must already be an `owner` of the workspace (409
        `titular_precisa_ser_owner` otherwise). From the commit on, agent turns
        look up the NEW owner of record's credential; if they have none for the
        model provider, the turn ends with the existing "no credential
        registered" outcome for that provider — this route does not check it
        beforehand. Any `owner` may call it, including for another owner.
        Transferring to the current owner of record is a no-op.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role in the workspace.
        '404':
          description: Workspace doesn't exist or is invisible to the caller.
        '409':
          description: >-
            `titular_precisa_ser_owner`: the target is not an `owner` of the
            workspace.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Transfers the workspace's owner of record
      tags:
        - workspaces
  /workspaces/{workspaceId}/project-folders:
    get:
      description: >-
        The folder picker for Mounted mode, served by the api instead of by the
        runner (RN-504). Scoped HARD to `BRABO_PROJECTS_BASE` (ADR 0141): `path`
        is optional and defaults to the base itself, and any `path` outside it
        is a `400` — a malformed request, not a permission problem, since no
        role browses outside the base.


        Capped on purpose: directories only, at most 500 of them (sorted BEFORE
        the cut, with `truncado` saying so), never recursive, dot-prefixed
        entries excluded, and symlinks reported but never followed. What is left
        out is COUNTED (`arquivos`, `simbolicos`) so a folder full of code never
        looks empty.


        There is no POST companion: creating a folder belongs to materializing
        the mounted workspace, never to the picker.
      operationId: WorkspacesController_listProjectFolders
      parameters:
        - name: path
          required: false
          in: query
          description: >-
            Absolute path to list. Omitted means the base itself. Must be inside
            `BRABO_PROJECTS_BASE`; `..`, `.` and relative paths are refused
            outright rather than resolved.
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectFoldersResponseDto'
        '400':
          description: The path is outside the projects base, or malformed.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role in the workspace.
        '404':
          description: Workspace doesn't exist or is invisible to the caller.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Lists the subfolders of a folder inside the projects base
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role in the workspace.
        '404':
          description: Workspace doesn't exist or is invisible to the caller.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Lists the workspace's projects
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role in the workspace.
        '404':
          description: Workspace doesn't exist or is invisible to the caller.
        '409':
          description: A project with this slug already exists in the workspace.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Creates a project inside the workspace
      tags:
        - workspaces
  /workspaces/{workspaceId}/projects-base:
    get:
      description: >-
        The single folder on the operator machine that the api and engine
        containers can see, mounted by identity (ADR 0141). `null` — a normal
        state, never an error — means this installation has no
        `BRABO_PROJECTS_BASE`, so the project wizard must not offer Mounted mode
        at all. The same value for every workspace: it is installation
        configuration, and `workspaceId` only scopes the authorization.


        `brokerConfigurado` answers the other half of the same question:
        `container` and `mounted` only start a container through the broker (ADR
        0144), so without it the wizard does not pre-select Mounted and offers
        Runner instead (ADR 0161, RN-573).
      operationId: WorkspacesController_getProjectsBase
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectsBaseResponseDto'
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role in the workspace.
        '404':
          description: Workspace doesn't exist or is invisible to the caller.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: The base folder for projects in Mounted mode
      tags:
        - workspaces
  /workspaces/{workspaceId}/projects-status:
    get:
      description: >-
        One row per workspace project with a blocked task — feeds the dashboard
        sidebar's status dot with a single query instead of N calls to the
        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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role in the workspace.
        '404':
          description: Workspace doesn't exist or is invisible to the caller.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Count of blocked tasks per project
      tags:
        - workspaces
  /workspaces/{workspaceId}/projects-summary:
    get:
      description: >-
        One row per workspace project with git provider, provisioning status,
        budget, last activity, stories awaiting promotion, and the facts that
        decide the agent roster (RN-090).


        Same idea as `projects-status`, one floor up: the card grid is a
        WORKSPACE read, not N project reads. The previous path made seven polled
        queries per card, and with 23 projects the dashboard alone blew through
        the 300 req/min rate limit.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role in the workspace.
        '404':
          description: Workspace doesn't exist or is invisible to the caller.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Everything the dashboard cards render, in one call
      tags:
        - workspaces
  /workspaces/{workspaceId}/spend-report:
    get:
      description: >-
        The OWNER's audience. The PROVIDER axis came back (ADR 0076, RN-186) and
        is here, not on the member's route, because breaking down by provider is
        breaking down by CREDENTIAL — and credential is whoever pays's business
        (RN-060). Complements `credential-spend`, which keeps answering the
        INVOICE question (per month, with the tie to the key that exists today).
        Person and agent also come in separate blocks, by `actor_kind`. Sliding
        window in days, and the daily series comes DENSE — a day with no spend
        comes in as zero, otherwise the sparkline lies about the pace.
      operationId: SpendController_getWorkspaceSpendReport
      parameters:
        - name: workspaceId
          required: true
          in: path
          schema:
            type: string
        - name: dias
          required: false
          in: query
          description: Sliding window. Default 30, max 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: No token, expired token, or invalid signature.
        '403':
          description: Requires `owner` on the workspace.
        '404':
          description: Project not in this workspace.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: >-
        Breaks down the workspace's spend by model, provider, project, actor,
        and day
      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: Sliding window. Default 30, max 180.
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MyWorkspaceSpendResponseDto'
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: My own consumption in this workspace, summed by project
      tags:
        - llm
  /workspaces/{workspaceId}/summary:
    get:
      description: >-
        Project count, agents that spent tokens this month, and this month's
        spend — feeds the summary line of the project dashboard.
      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: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role in the workspace.
        '404':
          description: Workspace doesn't exist or is invisible to the caller.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Aggregated workspace summary
      tags:
        - workspaces
  /workspaces/{workspaceId}/tool-router:
    put:
      description: >-
        On by default. When on AND the turn model is from OpenRouter, an agent
        step with two or more tools first asks the Jev which tool fits, and the
        chat model is offered only that tool and the previous one. The Jev never
        approves or denies anything: an action that needs approval still does.
        Any failure of the Jev falls back to the whole catalog. Turning it off
        stops every call to the Jev, and its cost with them.
      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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role in the workspace.
        '404':
          description: Workspace doesn't exist or is invisible to the caller.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Turns the Jev tool routing on or off for the workspace
      tags:
        - workspaces
  /workspaces/{workspaceId}/unread-events:
    post:
      description: >-
        The bell's drawer, for the whole workspace (RN-091). Each cursor says up
        to which `seq` that project has already been read, and the response
        carries what came after that in each one's most recent session.


        **It's POST because it's the only verb with a BODY, not because it
        changes state.** The server doesn't know where each reader stopped:
        "read up to here" is a `seq` per project stored in the browser, and
        there is (deliberately) no mark-as-read endpoint. So the cutoff needs to
        TRAVEL in the request, and there are dozens of pairs — in a query string
        that becomes a long URL, which a proxy truncates, and would also put the
        user's project ids in access logs. The route is idempotent and has no
        side effect: it responds `200`, never `201`.


        Replaces one request PER PROJECT with the drawer open — 23 projects cost
        286 req/min against a limit of 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: >-
            Invalid body. The `ValidationPipe` runs with `whitelist` and
            `forbidNonWhitelisted`, so an unknown field also fails.
        '401':
          description: No token, expired token, or invalid signature.
        '403':
          description: Insufficient role in the workspace.
        '404':
          description: Workspace doesn't exist or is invisible to the caller.
        '429':
          description: Rate limit per user or per IP.
      security:
        - bearer: []
      summary: Unread events across multiple projects, in one call
      tags:
        - workspaces
info:
  title: Brabo API
  description: >-
    The Brabo API. Every endpoint returns JSON, except the four explicitly

    documented with a different `content-type` (two SSE ones, the metrics one,

    and the OAuth callback, which redirects).


    ## Authentication


    The access token is a **15-minute EdDSA JWT**, presented in

    `Authorization: Bearer <token>`. The public key lives at

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


    The refresh **is not** a JWT: it's an opaque token that lives in an

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

    never appears in any response body. `POST /auth/refresh` and

    `POST /auth/logout` read the cookie and require the `X-CSRF-Token` header

    to match the `brabo_csrf` cookie.


    **Rotation is mandatory.** Each refresh consumes the presented token and

    issues another. Re-presenting an already-consumed one is a theft signature

    and revokes the WHOLE family — including the legitimate session. A client

    that fires two concurrent refreshes takes itself down; use a single
    in-flight

    call.


    The `/internal/*` routes are the api ↔ engine surface and sit **outside the

    JWT**: they require the `X-Brabo-Service-Token` header with the shared

    secret. A user token doesn't open any of them.


    ## Errors


    NestJS default format: `{ "statusCode": number, "message": string |
    string[], "error": string }`.


    | code | when |

    |---|---|

    | `400` | invalid body — the `ValidationPipe` runs with `whitelist` and
    `forbidNonWhitelisted`, so an unknown field also fails |

    | `401` | no token, expired token, or invalid credential |

    | `403` | authenticated but without the role RBAC requires, or a wrong
    service token |

    | `404` | resource doesn't exist **or** is invisible to the caller |

    | `409` | state conflict — invalid session transition, resource already
    exists |

    | `429` | rate limit or progressive lockout |


    Auth routes respond **deliberately indistinguishably** between a nonexistent

    email, a wrong password, and a locked account: same body, same status, same

    timing. That's what closes account enumeration, and it's not a diagnostic

    defect.


    ## Rate limit


    Sliding window in Postgres: 300 requests per user and 600 per IP every 60s,

    configurable. A `@Public()` route and a service route are **exempt** — what

    guards `/auth/*` is the progressive lockout, not the rate limit.
  version: 0.1.0
  contact: {}
tags:
  - name: auth
    x-displayName: auth
  - name: gates
    x-displayName: gates
  - name: git
    x-displayName: git
  - name: infrastructure
    x-displayName: infrastructure
  - name: internal
    x-displayName: internal
  - name: llm
    x-displayName: llm
  - name: projects
    x-displayName: projects
  - name: actions
    x-displayName: actions
  - name: execution
    x-displayName: execution
  - name: anamnesis
    x-displayName: anamnesis
  - name: backlog
    x-displayName: backlog
  - name: psychologist
    x-displayName: psychologist
  - name: rag
    x-displayName: rag
  - name: sessions
    x-displayName: sessions
  - name: agents
    x-displayName: agents
  - name: credentials
    x-displayName: credentials
  - name: users
    x-displayName: users
  - 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: >-
            Module name, as it appears in the `module_map`. Starts a dev agent
            dedicated to it, in an isolated worktree.
      required:
        - module
    AceiteAutomaticoResponseDto:
      type: object
      properties:
        aceito:
          type: boolean
          example: true
        criterio:
          $ref: '#/components/schemas/CriterioDoAceiteResponseDto'
        motivo:
          type: string
          enum:
            - nao_e_po_para_arquiteto
            - oferta_nao_pendente
            - sem_regras_de_negocio
            - regras_sem_historia
            - repositorio_nao_local
            - credencial_de_git_no_projeto
            - autor_sem_papel
            - falhou
          example: regras_sem_historia
          description: >-
            Why a person still has to click. `falhou`: the system tried and the
            accept failed — `handoff.auto_accept_failed` is in the event log.
      required:
        - aceito
    AceiteResponseDto:
      type: object
      properties:
        message:
          type: string
          example: If the address is available, we've sent an email.
          description: >-
            Generic message, identical for a known and an unknown address — see
            the enumeration note in the reference.
      required:
        - message
    ActivateExecutionDto:
      type: object
      properties:
        taskBudgetMicros:
          type: number
          example: 500000
          description: >-
            Budget per task in micro-USD. Omitted uses the domain default
            (DEFAULT_TASK_BUDGET_MICROS).
        maxGateCorrections:
          type: number
          example: 3
          description: >-
            How many dev↔gate round trips before the task becomes `blocked`. It
            is what stops an agent from spending its budget in a correction
            loop.
        devAgentImpl:
          type: string
          enum:
            - real
            - noop
          example: real
          description: >-
            `noop` starts the dev agents WITHOUT an LLM, to exercise the
            execution infrastructure end to end without spending tokens. Omitted
            uses `real`.
        terminalAllowPatterns:
          example:
            - Terminal(pnpm test:*)
            - Terminal(pnpm lint)
          description: >-
            Patterns released in `permissions.json` for the dev agents to run
            the test suite. `deny` still wins over anything listed here.
          type: array
          items:
            type: string
        originSessionId:
          type: string
          example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
          description: >-
            Id of the CHAT session the "activate execution" click came from —
            the Dev Lead/PO's creative/consultative session, never the execution
            one. Omitted (legacy caller, e.g. activation from the Overview
            without session context) closes no session, same as before. When
            given, it is closed at the end — but only if it has no pending
            handoff/action/turn (RN-135), and never if it is the execution
            session itself.
    ActorDto:
      type: object
      properties:
        kind:
          type: string
          enum:
            - user
            - agent
            - system
          example: agent
          description: Nature of who acted.
        id:
          type: string
          example: dev-api
          description: The user's id when `kind=user`; the agent's slug when `kind=agent`.
      required:
        - kind
        - id
    ActorResponseDto:
      type: object
      properties:
        kind:
          type: string
          enum:
            - user
            - agent
            - system
          example: agent
          description: Nature of who acted.
        id:
          type: string
          example: dev-api
          description: The user's id when `kind=user`; the agent's slug when `kind=agent`.
      required:
        - kind
        - id
    AddMemberDto:
      type: object
      properties:
        userId:
          type: string
          format: uuid
          example: 3f1b2c8e-5a4d-4b7e-9c10-2d6f8a1b4c33
          description: Id of the user to associate.
        role:
          type: string
          enum:
            - viewer
            - developer
            - maintainer
            - owner
          example: developer
          description: >-
            Role in this association. On a PROJECT it OVERRIDES the workspace
            role in both directions — the effective role is this one whenever
            the association exists, higher OR lower — and two movements are
            refused with 403: downgrading a workspace `owner`, and changing your
            own role (either way). On a WORKSPACE it is simply the role, with
            only the second of those caps: you cannot change your own.
      required:
        - userId
        - role
    AdoptRepositoryDto:
      type: object
      properties:
        externalId:
          type: string
          example: acme/checkout
          description: >-
            The repository's identifier ON THE PROVIDER: `owner/repo` on github,
            `namespace/path` on gitlab, the bare repo's absolute path locally.
            Nothing is created — the repository must already exist and the
            registered credential must be able to reach it.
      required:
        - externalId
    AdoptRepositoryResponseDto:
      type: object
      properties:
        repository:
          $ref: '#/components/schemas/ProvisionedRepositoryResponseDto'
        plan:
          description: >-
            The DRY-RUN: nothing was executed on the repository. Decide via
            `POST .../bootstrap/plan/approve` or `.../plan/skip`.
          allOf:
            - $ref: '#/components/schemas/BootstrapPlanResponseDto'
        alreadyAdopted:
          type: boolean
          example: false
          description: >-
            `true` when the project had already adopted THIS repository —
            nothing was created, the plan was just regenerated over the current
            state.
      required:
        - repository
        - plan
        - alreadyAdopted
    AdrRefResponseDto:
      type: object
      properties:
        actionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRC
          description: Id of the `open_adr_pr` `proposed_action` that opened the 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` while the PR has not actually been opened.'
      required:
        - actionId
        - title
        - status
        - pullRequestUrl
    AgentAutonomyRuleResponseDto:
      type: object
      properties:
        agentId:
          type: string
          example: dev-api
          description: Agent slug.
        actionType:
          type: string
          example: terminal
          description: >-
            The action type, or `"*"` — "auto mode" (RN-153): autonomy for ANY
            action type of this agent.
        mode:
          type: string
          enum:
            - auto_approve
            - require_approval
            - deny
          example: auto_approve
          description: >-
            Autonomy granted. Does not override `permissions.json`: a `deny`
            there still wins.
      required:
        - agentId
        - actionType
        - mode
    AgenteAtivadoResponseDto:
      type: object
      properties:
        agent:
          type: string
          example: po
          description: Slug of the activated agent.
        status:
          type: string
          example: active
          enum:
            - active
      required:
        - agent
        - status
    AgenteComVersoesResponseDto:
      type: object
      properties:
        agent:
          type: string
          example: dev-api
          description: >-
            Real slug, including the per-module dev agents. The listing does NOT
            start from a static roster — if it did, the agents that actually
            exist would not show up.
        versions:
          type: array
          items:
            $ref: '#/components/schemas/InstructionVersionResponseDto'
      required:
        - agent
        - versions
    AgentTokenUsageResponseDto:
      type: object
      properties:
        actorId:
          type: string
          example: dev-api
          description: The agent's slug, or the user's id.
        costMicros:
          type: number
          example: 3400000
          description: Cost in 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: >-
            The ORIGIN of the failure, never deduced by elimination (lesson from
            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: >-
            CLOSED catalog. The model cannot emit a competency outside of this —
            validation rejects it — so it goes into the prompt as a list.
          type: array
          items:
            type: string
        members:
          description: Already EXCLUDES whoever opted out of being profiled.
          type: array
          items:
            $ref: '#/components/schemas/AnamneseMemberResponseDto'
        queuedHypotheses:
          description: >-
            The Psychologist's ACCEPTED hypotheses waiting to become prioritized
            input.
          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: >-
            The user's approvals and denials within the window. They come
            through here because they are NOT in the event log.
          type: array
          items:
            $ref: '#/components/schemas/AnamneseDecisionResponseDto'
        windowFrom:
          type: object
          example: '2026-07-27T12:00:00.000Z'
          nullable: true
          description: >-
            Start of the window to analyze (end of the last round). `null` on
            the first one. The engine reads the events themselves straight from
            Postgres — carrying them over HTTP would be more expensive without
            being more correct.
      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: >-
            The language the question was about — the `detectionQuestion` of
            `GET .../sessions/:sessionId/response-language`.
        answer:
          type: string
          enum:
            - confirm
            - decline
          example: confirm
          description: >-
            `confirm` stores it as your detected-and-confirmed language (it then
            answers you wherever your Account is on automatic); 409
            `deteccao_mudou` when your messages no longer point to it. `decline`
            stores the refusal and this language is never asked again
            (confirming it later clears the refusal).
      required:
        - language
        - answer
    AnswerOpenQuestionDto:
      type: object
      properties:
        resposta:
          type: string
          example: 404 for a code that does not exist.
          description: 'Free-text answer. Send this OR `aceitarSugestao: true`.'
        aceitarSugestao:
          type: boolean
          example: true
          description: Accept the agent's suggestion as the answer.
    AnswerStructuredQuestionDto:
      type: object
      properties:
        answers:
          type: object
          additionalProperties:
            type: string
          example:
            name: Easy Checkout
            users: Small business owners
            platform: Web
          description: >-
            Answer keyed by question `id` — the same `id` that came in
            `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: Event type. The full inventory is in `docs/reference/events.md`.
        actor:
          $ref: '#/components/schemas/ActorDto'
        payload:
          type: object
          additionalProperties: true
          example:
            role: user
            content: I want a new checkout.
          description: Free-form shape, specific to each `type`.
      required:
        - type
        - actor
        - payload
    AppendSessionEventInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        type:
          type: string
          example: agent.tool_call
          description: See `docs/reference/events.md`.
        actorKind:
          type: string
          enum:
            - user
            - agent
            - system
          example: agent
        actorId:
          type: string
          example: dev-api
          description: Slug of the agent that emitted it.
        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 of the action.
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        sessionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
        seq:
          type: number
          example: 7
          description: >-
            Order of the action within the session; used as a cursor in
            listings.
        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: >-
            What the action would do. `git_push` and `git_merge` require
            `maintainer`.
        payload:
          type: object
          additionalProperties: true
          example:
            command: pnpm test
          description: Parameters of the action, specific to the `actionType`.
        status:
          type: string
          enum:
            - pending
            - approved
            - denied
            - auto_approved
            - executed
            - failed
          example: pending
          description: >-
            State in the pipeline. `auto_approved` is distinct from `approved`
            on purpose: the log needs to distinguish what a person decided from
            what the policy released.
        resolvedPolicy:
          type: string
          enum:
            - auto_approve
            - require_approval
            - deny
          example: require_approval
          description: >-
            What `permissions.json` decided for this action. `deny` ALWAYS wins
            over `allow` — not even the agent's autonomy overrides it.
        actor:
          description: Who proposed it.
          allOf:
            - $ref: '#/components/schemas/ActorResponseDto'
        decidedBy:
          type: object
          example: null
          nullable: true
          description: Id of the user who approved or denied it. Never an agent.
        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: >-
            Execution result, with a shape specific to each `actionType` —
            output and exit code for terminal, PR number and URL for the git
            types. `null` while the action has not been executed.
        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`: this click approved the action. `ja_aprovada`: the
            action had already left `pending` by an approval (a previous click,
            another person, or the policy) — idempotent success, not an error. A
            DENIED action is still 409 `acao_ja_recusada`.
        padraoGravado:
          type: boolean
          example: true
          description: >-
            Whether this click recorded the pattern (and its
            `permission.granted` event). `false` means it already existed;
            nothing was written.
      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`: the story doesn't point at any module.
            `missing_module`: it points at one that doesn't exist in the current
            map.
        missing:
          example:
            - pagamentos
          description: The cited modules that don't exist. Empty when `no_module`.
          type: array
          items:
            type: string
      required:
        - storyId
        - title
        - status
        - reason
        - missing
    ArchitectureResponseDto:
      type: object
      properties:
        moduleMap:
          nullable: true
          description: '`null` while the Architect has not written the first map.'
          type: object
          allOf:
            - $ref: '#/components/schemas/ModuleMapResponseDto'
        adrs:
          type: array
          items:
            $ref: '#/components/schemas/AdrRefResponseDto'
        pendencies:
          description: Story↔map cross-validation. An empty list is the healthy state.
          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: Optional; recorded in `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: Area identifier. Unique within the project.
        leadAgentId:
          type: string
          example: dev-lead
          description: The area's lead — its only external contact.
        maxParallel:
          type: number
          example: 2
          description: >-
            How many agents the lead can have in the SESSION without requesting
            authorization. Above that, a `proposed_action`. The cap is per
            session, not per module: counting per module would allow N modules ×
            N agents with no authorization at all.
        budgetMicros:
          type: object
          example: 20000000
          nullable: true
          description: >-
            The area's spend cap, in micro-USD (ADR 0110, RN-443). `null` means
            no cap — an independent, ADDITIVE check next to the project and
            session budgets, not a cascade: whichever of the three hits its cap
            first blocks the call.
        spentMicros:
          type: number
          example: 4300000
          description: >-
            The area's accumulated spend, in micro-USD. Increments whenever an
            agent that belongs to this area spends, WITH OR WITHOUT a cap set.
        members:
          example:
            - dev-api
            - dev-web
          description: >-
            The members. In the dev area they come from the `module_map`, one
            per module — that is what keeps the area from being a fixed list in
            code.
          type: array
          items:
            type: string
      required:
        - id
        - projectId
        - key
        - leadAgentId
        - maxParallel
        - budgetMicros
        - spentMicros
        - members
    ArquivosTocadosResponseDto:
      type: object
      properties:
        itens:
          description: Most recent first, capped at 100.
          type: array
          items:
            $ref: '#/components/schemas/ArquivoTocadoResponseDto'
        total:
          type: number
          example: 140
          description: Real total, even when `itens` was cut.
        prsNaoLidas:
          type: number
          example: 0
          description: >-
            PRs of the read sessions whose files were NOT read: past the 20-PR
            cap, or the provider refused the 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`) plus epics, stories and tasks this
            agent created in the session.
      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: >-
            Names from the current `module_map`. A module that doesn't exist
            becomes an architecture pending item.
          type: array
          items:
            type: string
      required:
        - projectId
        - storyId
        - moduleIds
    AttachLocalFolderRequestDto:
      type: object
      properties:
        folderName:
          type: string
          example: brabo
          description: >-
            The folder name as chosen in the browser — shown in the coverage
            panel.
        files:
          description: >-
            Up to 500 files. The exact summed-byte cap
            (RAG_LOCAL_TOTAL_BYTES_LIMIT) and the per-file extension allowlist
            are enforced by the use case, not by this DTO — an array of
            individually-valid strings can still add up to a payload too large.
          type: array
          items:
            $ref: '#/components/schemas/LocalFolderFileDto'
      required:
        - folderName
        - files
    AttachLocalFolderResponseDto:
      type: object
      properties:
        folderName:
          type: string
        filesIndexed:
          type: number
        filesSkipped:
          type: number
          description: >-
            Skipped for being over the per-file byte cap or having an
            unrecognized extension — never fails the whole batch.
        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: The agent slug or the area key, exactly as requested.
        binding:
          nullable: true
          description: >-
            The SAME value the single-key route answers for this key — `null`
            when no level of the cascade has a model.
          type: object
          allOf:
            - $ref: '#/components/schemas/ResolvedBindingResponseDto'
      required:
        - key
        - binding
    BindingsResolvidosEmLoteResponseDto:
      type: object
      properties:
        agents:
          description: One entry per requested agent, in the order requested.
          type: array
          items:
            $ref: '#/components/schemas/BindingResolvidoDaChaveResponseDto'
        areas:
          description: One entry per requested area, in the order requested.
          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: >-
            Free-text diagnosis — a human detail of what happened, not the
            structured origin of the failure.
        origin:
          type: string
          enum:
            - infra
            - modelo
            - codigo
            - politica
          example: infra
          description: >-
            The ORIGIN of the failure: `infra`, `modelo`, `codigo`, or
            `politica`. Never by elimination — this is the lesson from ADR 0020,
            taken up a level by the QA Lead in ADR 0038. Optional: not every
            call site knows the origin.
      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` is INFORMATIONAL and never blocks: an adopted
            repository has whatever policy it has, and the bootstrap doesn't
            delete it.
        detail:
          type: object
          example:
            branchName: develop
            protected: false
          additionalProperties: true
      required:
        - kind
        - detail
    BootstrapPlanEstadoResponseDto:
      type: object
      properties:
        plan:
          nullable: true
          description: '`null` when the project has no adopted repository.'
          type: object
          allOf:
            - $ref: '#/components/schemas/BootstrapPlanResponseDto'
        decision:
          type: string
          enum:
            - approved
            - as_is
          example: null
          nullable: true
          description: >-
            `null` = plan generated and NOT yet decided. This is the state in
            which nothing runs (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: >-
            What the bootstrap WOULD DO. An empty list = the repository is
            already how the template wants it, and there is nothing to approve.
          type: array
          items:
            $ref: '#/components/schemas/BootstrapPlanStepResponseDto'
        diagnostics:
          description: The divergences between the repository and the 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: >-
            The same taxonomy as `proposed_actions` — each approved step becomes
            a recorded action when the bootstrap runs.
        payload:
          type: object
          example:
            branchName: qa
            fromRef: dev
          description: 'The mutation data: which branch, which file, from where.'
          additionalProperties: true
      required:
        - step
        - actionType
        - payload
    BudgetResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000ORCAMENTO000000001
        projectId:
          type: object
          example: 01JC4Z0000PROJETO0000000001
          nullable: true
          description: Filled in for a project budget; `null` for a session one.
        sessionId:
          type: object
          example: null
          nullable: true
          description: The opposite of the above.
        limitMicros:
          type: number
          example: 50000000
          description: Cap in micro-USD (50 USD).
        spentMicros:
          type: number
          example: 12400000
          description: Accumulated spend, in micro-USD.
        policy:
          type: string
          enum:
            - block
            - allow
          example: block
          description: >-
            `block` refuses the call when it would exceed the cap; `allow` lets
            it through and only warns.
        lastThresholdNotified:
          type: number
          example: 70
          description: >-
            Highest threshold (70/90/100) already notified. It, not the previous
            spend, serves as the floor — using the spend would make the same
            warning fire again on every call after it was crossed.
        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` (default) for whoever operates the system;
            `external_system` for another system it talks to (e.g. a Git
            provider).
        description:
          type: string
          example: Whoever operates the product via the web.
      required:
        - name
    C4AtorResponseDto:
      type: object
      properties:
        name:
          type: string
          example: User
        type:
          type: string
          enum:
            - person
            - external_system
          example: person
        description:
          type: string
          example: Whoever operates the product through the web.
      required:
        - name
        - type
        - description
    C4DiagramaGeradoResponseDto:
      type: object
      properties:
        diagrama:
          $ref: '#/components/schemas/C4DiagramaResponseDto'
        version:
          type: number
          example: 1
      required:
        - diagrama
        - version
    C4DiagramaResponseDto:
      type: object
      properties:
        systemName:
          type: string
          example: Brabo
        systemDescription:
          type: string
          example: Agent-orchestrated engineering platform.
        actors:
          type: array
          items:
            $ref: '#/components/schemas/C4AtorResponseDto'
        contextDiagram:
          type: string
          example: |-
            C4Context
              title Context Diagram -- Brabo
              ...
          description: Mermaid `C4Context` syntax — the system and the external actors.
        containerDiagram:
          type: string
          example: |-
            C4Container
              title Container Diagram -- Brabo
              ...
          description: >-
            Mermaid `C4Container` syntax — the current module_map's modules and
            the dependencies between them.
      required:
        - systemName
        - systemDescription
        - actors
        - contextDiagram
        - containerDiagram
    ChatSseEventResponseDto:
      type: object
      properties:
        type:
          type: string
          enum:
            - delta
            - done
            - error
            - metering_failed
          example: delta
          description: >-
            `delta` carries incremental `text`; `done` closes with the
            token/cost accounting; `error` aborts; `metering_failed` means the
            RESPONSE went out but the cost was not accounted for — the frame
            exists so that failure does not go unnoticed.
        text:
          type: string
          example: I will open the epic
          description: Only on `delta`.
        inputTokens:
          type: number
          example: 1820
          description: Only on `done`.
        outputTokens:
          type: number
          example: 340
          description: Only on `done`.
        costMicros:
          type: number
          example: 52700
          description: Only on `done`, micro-USD.
        estimated:
          type: boolean
          example: false
          description: >-
            Only on `done`. `true` when the provider did not return a count and
            the cost was ESTIMATED — the number is worth less and the UI needs
            to be able to say so.
        message:
          type: string
          example: session budget exhausted
          description: Only on `error` and `metering_failed`.
      required:
        - type
    CicloDeVidaDoContainerResponseDto:
      type: object
      properties:
        status:
          type: string
          enum:
            - provisioning
            - running
            - stopped
            - failed
            - removed
          example: provisioning
          description: >-
            What was RECORDED, not what a Docker daemon confirms — no real
            orchestrator transitions this table today (RN-243).
        imageVersion:
          type: number
          example: 1
          description: >-
            Version of `artifact.project_image` FROZEN at the first transition
            (RN-245) — not reapplied on every read.
        resources:
          description: Cap DECLARED at provisioning time — not enforced (RN-248).
          allOf:
            - $ref: '#/components/schemas/RecursosDoContainerResponseDto'
        failureReason:
          type: object
          nullable: true
          example: null
          description: Only populated on a transition to `failed`.
        createdAt:
          type: string
          format: date-time
        statusChangedAt:
          type: string
          format: date-time
        observado:
          nullable: true
          description: >-
            What the Docker daemon reports RIGHT NOW, asked through the broker
            (ADR 0130) — never fused with the fields above, which are what was
            RECORDED. A container killed from the outside shows up as recorded
            `running` and observed `exited`, and that is the point. `null` means
            either "there is no container" or "I could not look" — read
            `naoObservado` to tell them apart.
          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` when the observation actually HAPPENED — including when it
            came back empty, which is the positive statement "I looked and there
            is no container". Filled in when there was no way to look, saying
            why. Inheriting the recorded state here is exactly what RN-468
            forbids.
        detalheDaObservacao:
          type: object
          nullable: true
          example: null
          description: The broker's own message, when there was one.
      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: Only tasks from this module enter the draw.
        agentId:
          type: string
          example: dev-api
      required:
        - projectId
        - module
        - agentId
    CloseAnamneseWindowInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        windowFrom:
          type: string
          example: '2026-07-27T12:00:00.000Z'
          format: date-time
        windowTo:
          type: string
          example: '2026-07-27T12:15:00.000Z'
          format: date-time
        eventCount:
          type: number
          example: 12
      required:
        - projectId
        - windowFrom
        - windowTo
        - eventCount
    CodeBlameLineResponseDto:
      type: object
      properties:
        line:
          type: number
          example: 12
          description: 1-based, like every editor shows.
        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: The file went past the per-call annotated-lines cap.
      required:
        - ref
        - path
        - lines
        - truncated
    CodeBranchDetailListResponseDto:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/CodeBranchDetailResponseDto'
        truncated:
          type: boolean
          example: false
          description: The list was cut at the enriched-branches cap.
      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 ahead of the default branch. `null` when not computable.
        behind:
          type: number
          nullable: true
          example: 0
          description: Commits behind the default branch.
        pullRequest:
          nullable: true
          type: object
          allOf:
            - $ref: '#/components/schemas/CodeBranchPullRequestRefResponseDto'
        producedBy:
          nullable: true
          description: >-
            Dev agent/module owning the branch (RN-152), when the name matches
            `feature/task-XXXXXXXX` and the task/module are still resolvable.
            `null` for a user's manual branch or for `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 of the dev who created the branch
            (`dev-<module>`/`dev-<module>-2`, RN-087).
        moduleId:
          type: string
          example: pieces
          description: Module name, from the project's current `module_map`.
      required:
        - agentId
        - moduleId
    CodeBranchPullRequestRefResponseDto:
      type: object
      properties:
        number:
          type: number
          example: 42
        state:
          type: string
          enum:
            - open
            - merged
            - closed
          example: open
      required:
        - number
        - state
    CodeDiffFileResponseDto:
      type: object
      properties:
        path:
          type: string
          example: apps/api/src/main.ts
          description: Path AFTER the change (for `removed`, what disappeared).
        previousPath:
          type: string
          nullable: true
          example: null
          description: Previous path; only filled in when `status` is `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: >-
            The file's unified diff. `null` when the provider doesn't deliver it
            (binary, or the patch is too large) — distinct from `""`, which is
            "came back empty". Without this distinction the screen would say "no
            changes" on a changed binary.
      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: The list was cut at the per-diff file cap.
      required:
        - pullRequestId
        - files
        - truncated
    CodeFileResponseDto:
      type: object
      properties:
        ref:
          type: string
          example: dev
        path:
          type: string
          example: apps/api/src/main.ts
        content:
          type: string
          example: |
            import { NestFactory } from '@nestjs/core';
          description: UTF-8 content. Binary is not served by this route.
        truncated:
          type: boolean
          example: false
          description: >-
            The file went past the byte cap and `content` is the beginning of
            it. The tab is read-only: truncating and warning is better than
            refusing the file.
        bytes:
          type: number
          example: 1284
          description: Bytes RETURNED — after the cut, not before.
      required:
        - ref
        - path
        - content
        - truncated
        - bytes
    CodePullRequestListResponseDto:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/CodePullRequestSummaryResponseDto'
        truncated:
          type: boolean
          example: false
          description: The list was cut at the per-call PR cap.
      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 of who opened it. `null` when the provider doesn't
            report it.
        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` when the provider doesn''t report it.'
      required:
        - id
        - number
        - title
        - url
        - author
        - state
        - sourceBranch
        - targetBranch
        - updatedAt
    CodeSearchMatchResponseDto:
      type: object
      properties:
        path:
          type: string
          example: apps/api/src/main.ts
        line:
          type: number
          example: 12
          description: 1-based, like every editor shows.
        text:
          type: string
          example: '  const app = await NestFactory.create(AppModule);'
          description: >-
            The whole line, trimmed so a minified file is never returned in
            full.
      required:
        - path
        - line
        - text
    CodeSearchResponseDto:
      type: object
      properties:
        ref:
          type: string
          example: dev
        path:
          type: string
          example: ''
          description: Subtree the search ran over.
        query:
          type: string
          example: NestFactory
        matches:
          type: array
          items:
            $ref: '#/components/schemas/CodeSearchMatchResponseDto'
        filesScanned:
          type: number
          example: 87
          description: >-
            Files actually opened. This is the search's real cost, and it is in
            the response so it doesn't stay invisible.
        truncated:
          type: boolean
          example: true
          description: >-
            The scan stopped by budget (directories, files, or matches) before
            finishing the tree. Narrow the `path` or the term.
      required:
        - ref
        - path
        - query
        - matches
        - filesScanned
        - truncated
    CodeTreeEntryResponseDto:
      type: object
      properties:
        path:
          type: string
          example: apps/api/src/main.ts
          description: Full path from the repository's root.
        name:
          type: string
          example: main.ts
          description: Last segment of `path`.
        type:
          type: string
          enum:
            - file
            - dir
          example: file
        size:
          type: number
          nullable: true
          example: 1284
          description: >-
            Bytes. `null` for a directory and when the provider doesn't report
            it — distinguishing it from `0` matters so the screen never claims
            "empty file".
      required:
        - path
        - name
        - type
        - size
    CodeTreeResponseDto:
      type: object
      properties:
        ref:
          type: string
          example: dev
          description: Branch, tag, or sha read.
        path:
          type: string
          example: apps/api
          description: Listed directory; `""` is the root.
        entries:
          type: array
          items:
            $ref: '#/components/schemas/CodeTreeEntryResponseDto'
        truncated:
          type: boolean
          example: false
          description: >-
            The listing was cut at the per-level entry cap. There is more in the
            directory than what came back here.
      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: >-
            ADDED to the rules the story already cites (completing never unlinks
            a rule); each new id has to reference an existing
            `artifact.business_rule`.
          type: array
          items:
            type: string
        questionId:
          type: string
          format: uuid
          description: >-
            RN-859 (ADR 0214): an open question the user ALREADY answered — only
            then a `ready` story (no task in execution) can be corrected.
      required:
        - projectId
    ConfirmacaoDeArquiteturaResponseDto:
      type: object
      properties:
        ok:
          type: boolean
          example: true
          enum:
            - true
        desfecho:
          type: string
          enum:
            - confirmado
            - ja_oferecido
          description: >-
            `confirmado`: the Infra was triggered. `ja_oferecido`: it already
            had a pending offer or was active in the project — nothing was
            recorded nor asked of the engine (double click, second tab). The Dev
            Lead is no longer a target here: the Infra offers it once the
            container is `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: >-
            Absolute path confirmed by the runner ON THE HOST — the source of
            truth (RN-423). Re-validated LEXICALLY here before writing; invalid
            is 400, never written.
        sessionId:
          type: string
          format: uuid
          example: 01JC4Z0000SESSAO00000000001
          description: >-
            The project's most recent session (`ProjectSession.latest_id/1`),
            resolved on the engine side. `null`/omitted when the project doesn't
            have any session yet — the project is still updated, only the audit
            event is skipped (accepted gap, RN-423).
        actorId:
          type: string
          format: uuid
          example: 01JC4Z0000USUARIO0000000001
          description: >-
            Who requested the runner ticket (owner of the socket that confirmed)
            — becomes the actor of the `project.workspace_verified` event.
            Omitted: the event (if there is a session) is born with no
            identified actor.
      required:
        - path
    ConfirmProjectWorkspaceResponseDto:
      type: object
      properties:
        verified:
          type: boolean
          example: true
          description: >-
            Always `true` when the call did not throw — confirmation is
            idempotent. `changed` says whether ANYTHING changed on this call.
        workspacePath:
          type: string
          example: /home/you/projects/store
          description: >-
            The WRITTEN (normalized) path — can differ from what creation had,
            because the runner is the source of truth and overwrites it.
        changed:
          type: boolean
          example: true
          description: >-
            `true` on the first confirmation, or when the reported path is
            DIFFERENT from what was already written. `false` on a reconnection
            that reports the same path as always — nothing was rewritten.
      required:
        - verified
        - workspacePath
        - changed
    ContainerExecInternalDto:
      type: object
      properties:
        comando:
          type: string
          example: npm test
          description: >-
            The shell command, interpreted by `sh -c` INSIDE the container —
            same convention as the terminal action run outside one.
        cwd:
          type: string
          example: /work/.worktrees/dev-api
          description: >-
            Working directory INSIDE the container. Already translated from the
            host path by the engine (RN-492) — this DTO doesn't repeat that
            translation. Omitted runs at `/work`, the project root. The broker
            re-validates it is inside `/work` and refuses
            (`DiretorioForaDoEscopoError`) otherwise.
        timeoutMs:
          type: number
          example: 300000
          description: Timeout in milliseconds. Omitted uses the broker default.
        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` when the broker refused or did not answer — the other fields
            are absent and `motivo` explains.
        exitCode:
          type: number
          example: 0
          description: 'Present only when `sucesso: true`.'
        output:
          type: string
          example: |
            ok
          description: 'Present only when `sucesso: true`.'
        timedOut:
          type: boolean
          example: false
          description: 'Present only when `sucesso: true`.'
        motivo:
          type: string
          example: 'o broker de container não respondeu em http://broker:8090: timeout'
          description: 'Present only when `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: >-
            Where this project's container comes up (RN-497/503). `container`
            and `mounted` go through the broker, on the server; `runner` goes
            through the local agent on the user's machine. It is what branches
            the start action between `container_start` and
            `container_start_via_runner`.
        registrado:
          nullable: true
          description: >-
            `null` means this project NEVER provisioned a container — a THIRD
            state, not a `status` value: it is neither `stopped` (a container
            that existed and stopped) nor "could not be observed" (which is
            about the daemon). Read `naoVerificado: "sem_container_registrado"`
            alongside it.
          type: object
          allOf:
            - $ref: '#/components/schemas/RegistroDeContainerResponseDto'
        temImagemDecidida:
          type: boolean
          example: true
          description: >-
            Whether ANY `artifact.project_image` exists for this project — the
            RN-105 gate, which since RN-494/ADR 0135 applies to all THREE modes.
            `false` means starting a container is impossible right now, and the
            page says so instead of proposing an action that is known to fail.
        workspaceVerifiedAt:
          type: object
          nullable: true
          format: date-time
          description: >-
            When a runner CONFIRMED the folder (RN-423) — only meaningful for
            `executionMode: "runner"`. It records ONE confirmation and is never
            a heartbeat (RN-468): non-null does NOT prove a local agent is
            connected now. What it does prove is the negative — `null` on a
            `runner` project means no runner ever connected.
        observado:
          nullable: true
          description: >-
            What the broker reports right now — `null` either because there is
            no container, because it could not be asked (`naoObservado`), or
            because this row was not asked this load (`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: >-
            Non-null when this row was NOT asked of the broker this load — never
            confused with `naoObservado`, which means it WAS asked and failed.
            `fora_do_escopo_da_verificacao`: status is
            `stopped`/`failed`/`removed`, where daemon confirmation does not
            matter. `teto_de_verificacoes_atingido`: eligible, but the per-load
            broker call budget was already spent by other rows.
            `sem_container_registrado`: the project never provisioned one, so
            there is nothing to observe — and it never spends a call from the
            budget either.
        acaoPendente:
          nullable: true
          description: >-
            The pending
            `container_start`/`container_stop`/`container_remove`/`container_start_via_runner`
            action for this project, if any — in ANY of its sessions. The page
            renders the inline `ApprovalCard` for it instead of the action
            button, same pattern as the PRs tab.
          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: >-
            Whether THIS INSTALLATION has a container broker configured
            (`BROKER_URL` set, ADR 0130) — the same value on every row, because
            it is installation configuration and not a property of the project.
            `container` and `mounted` projects only start a container through
            the broker (ADR 0144), so with `false` the page refuses the start
            button for them BEFORE the click and says why (ADR 0161, RN-574);
            `runner` projects are unaffected. It says the variable exists, never
            that the broker answers — that is what `naoObservado` reports.
      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: >-
            Folder name FROZEN at project creation (RN-109). It is the single
            source of the container name (`brabo-<workspaceDirName>`) in ALL
            three modes. Where the FOLDER is is a different question, answered
            by `localizacao` (RN-503) — the api still never sends an absolute
            path, because a path from inside the api container is not a path the
            Docker daemon can resolve.
        executionMode:
          type: string
          enum:
            - container
            - mounted
            - runner
          example: container
          description: >-
            Where the code lives. The broker serves `container` AND `mounted`
            (RN-503): since ADR 0141 the mounted folder lives under one base
            this server mounts by identity, so the daemon reaches it. It still
            refuses `runner`: that folder is on the user's machine and no root
            here sees it — there, the runner is what brings a container up (ADR
            0137).
        localizacao:
          description: >-
            The discriminated locator of the project folder (RN-503): which of
            the broker's two roots resolves it, and the relative segment to join
            to that root.
          allOf:
            - $ref: '#/components/schemas/LocalizacaoDoProjetoResponseDto'
        usuarioDaPasta:
          type: object
          nullable: true
          properties:
            uid:
              type: integer
              example: 1000
            gid:
              type: integer
              example: 1000
          description: >-
            The OWNER of the project folder, measured by the api (ADR 0180): the
            broker starts the container with `--user uid:gid` so the dev agent
            can write to the folder under `--cap-drop ALL`. `null` when the
            folder cannot be measured, the project is `runner`, or the owner is
            root — the container then starts as before. Never accepted from a
            request to the broker.
        imagem:
          nullable: true
          description: >-
            `null` while the Architect has not decided (RN-105) — `start` is
            then refused with 409 on the broker side, and the other four
            operations still work.
          type: object
          allOf:
            - $ref: '#/components/schemas/ImagemParaOBrokerResponseDto'
        imagemVersao:
          type: number
          example: 3
          description: >-
            Version of the current artifact; 0 when there is no decision. It
            ends up on the container as the `brabo.image.version` label.
        comandoDoApp:
          type: string
          nullable: true
          description: >-
            AT-610 (ADR 0228): the app command declared by the Infra in
            `run.app` (`artifact.run_instructions`), or `null`. Always `null`
            for `runner`.
        ports:
          type: array
          nullable: true
          items:
            type: object
            properties:
              host:
                type: integer
                example: 8080
              container:
                type: integer
                example: 3000
              protocol:
                type: string
                example: tcp
          description: >-
            AT-608: ports declared in `artifact.container_network`, published on
            127.0.0.1 only. `null` when undeclared or `runner`.
        dns:
          nullable: true
          description: 'AT-608: DNS servers (literal IPs), or `null`.'
          type: array
          items:
            type: string
        extraHosts:
          type: array
          nullable: true
          items:
            type: object
            properties:
              name:
                type: string
                example: api.local
              ip:
                type: string
                example: 10.0.0.5
          description: 'AT-608: `--add-host` entries (IPv4), or `null`.'
        qa:
          type: object
          properties:
            chave:
              type: string
              example: a1b2c3d4
            services:
              type: array
              items:
                type: object
          description: >-
            AT-612 (ADR 0229): ONLY with `papel=qa`. `chave` = 8 first [a-z0-9]
            of the task id; `services` = `run.qa.services` declared by the Infra
            (image with digest, env, tmpfs, user `<uid>:<gid>` never 0, command
            argv after the image, shared).
      required:
        - projectId
        - projectSlug
        - workspaceId
        - workspaceDirName
        - executionMode
        - localizacao
        - usuarioDaPasta
        - imagem
        - imagemVersao
        - comandoDoApp
        - ports
        - dns
        - extraHosts
    ContratoDeModuloInternalDto:
      type: object
      properties:
        modulo:
          type: string
          example: board-engine
          description: Module name — must exist in the current module_map.
        expoe:
          description: What the module exposes to whoever depends on it. 1 to 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: 'The current module_map''s `dependsOn`: what this module CONSUMES.'
          type: array
          items:
            type: string
        expoe:
          nullable: true
          description: >-
            What it exposes. `null` = the Architect declared no contract for
            this module (different from exposing nothing).
          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 when there is no contract.
        modulos:
          description: One per module of the CURRENT module_map, in map order.
          type: array
          items:
            $ref: '#/components/schemas/ContratoLidoDeModuloResponseDto'
        contratosForaDoMapa:
          example: []
          description: >-
            Contracts of modules the current module_map no longer has — said,
            never attached to a module that does not exist.
          type: array
          items:
            type: string
      required:
        - status
        - version
        - modulos
        - contratosForaDoMapa
    ConvertExecutionModeDto:
      type: object
      properties:
        executionMode:
          type: string
          enum:
            - container
            - mounted
            - runner
          example: runner
          description: >-
            The new execution mode. It may repeat the current project mode — in
            that case, only `workspacePath` really changes.
        workspacePath:
          type: string
          example: /home/you/projects/store
          description: >-
            The absolute path of the user folder — required when `executionMode`
            is `mounted` or `runner`, refused when it is `container`. Same
            validation as creation (RN-170/RN-422/RN-423): in `mounted`, it must
            exist and be writable from inside the container; in `runner`, only
            the format is validated now — the real confirmation comes from the
            runner connecting again.
      required:
        - executionMode
    CoverageReportResponseDto:
      type: object
      properties:
        rules:
          type: array
          items:
            $ref: '#/components/schemas/RuleCoverageResponseDto'
        uncoveredCount:
          type: number
          example: 2
          description: How many rules have no story at all. Each one is a discovery.
      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: Always an agent on this route.
          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: System/project name, for the diagram's label.
        systemDescription:
          type: string
          example: Agent-orchestrated engineering platform.
        actors:
          description: >-
            External actors of the Context level (Simon Brown). The Container
            level's containers do NOT go here: they come from the project's
            current module_map.
          type: array
          items:
            $ref: '#/components/schemas/C4AtorInternalDto'
      required:
        - projectId
        - systemName
    CreatedStoryResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000HISTORIA000000001
        epicId:
          type: string
          example: 01JC4Z0000EPICO000000000001
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        sessionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
        title:
          type: string
          example: Add item to cart
        description:
          type: string
          example: As a buyer, I want to gather items before paying.
        rf:
          example:
            - The cart accepts up to 50 items
          description: Functional requirements.
          type: array
          items:
            type: string
        rnf:
          example:
            - The response stays under 200 ms at p95
          description: Non-functional requirements.
          type: array
          items:
            type: string
        businessRuleIds:
          example:
            - RN-014
          description: >-
            Business rules covered. This is where rule→story coverage is
            computed in `GET /projects/:id/coverage`.
          type: array
          items:
            type: string
        dod:
          example:
            - Unit tests green
          description: Definition of done.
          type: array
          items:
            type: string
        dor:
          example:
            - Module defined
          description: Definition of ready.
          type: array
          items:
            type: string
        moduleIds:
          example:
            - api
          description: >-
            Modules from the `module_map` that the story touches. A story with
            no module, or with a nonexistent module, becomes an architecture
            pending item.
          type: array
          items:
            type: string
        status:
          type: string
          enum:
            - draft
            - ready
            - in_progress
            - done
          example: ready
        proposedReady:
          type: boolean
          example: true
          description: >-
            The PO finished the story and it's waiting on the user's decision
            (Phase 12c — RN-048). Coexists with `status: "draft"`: it is a
            proposal, not a state. Always `false` in a project in `auto` mode.
        returnedReason:
          type: object
          example: DoD too generic — spell out the acceptance criteria.
          nullable: true
          description: >-
            Why the user returned the story to the PO. `null` when it was never
            returned.
        returnedAt:
          type: object
          example: '2026-08-02T14:00:00.000Z'
          format: date-time
          nullable: true
        archivedAt:
          type: string
          example: null
          format: date-time
          description: >-
            When the story was ARCHIVED (RN-727). Archived stories leave the
            backlog, the coverage, the plan and the claim; `null` is a story in
            play.
          nullable: true
        archivedReason:
          type: string
          example: null
          description: Why it was archived, when someone said (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 of the session_event for the artifact that motivated the
            handoff.
        seAusente:
          type: boolean
          example: true
          description: >-
            Only if nobody got it yet (ADR 0182, RN-636): with a pending offer
            to the target in ANY session of the project, return it (`desfecho:
            ja_oferecido`) instead of replacing it. AppSec sends it.
      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: >-
            Module graph. A CYCLE makes the map get rejected with 400 — the
            validation is domain-level. Each module may declare `resources`
            (cpus, memoryMb, pidsLimit — all three or none); a partial
            declaration, a value above the container ceiling, or a SUM over
            modules above it is also 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. Unique within the workspace.
        maxConsecutiveBlocked:
          type: number
          example: 3
          description: >-
            Circuit breaker (Phase 12b — RN-047): how many consecutive tasks
            ending blocked stop the module's dev agent at idle_tripped. Omitted
            uses the domain default (DEFAULT_MAX_CONSECUTIVE_BLOCKED). Applies
            from the NEXT execution activation onward — doesn't affect agents
            already running.
        storyPromotion:
          type: string
          enum:
            - manual
            - auto
          example: auto
          description: >-
            Who promotes a story from `draft` to `ready` (Phase 12c — RN-048).
            `auto` (new-project default since RN-659): a complete story is
            already born `ready`, with no human step. `manual`: the PO proposes
            and YOU decide, in the Backlog tab. Changing the default did not
            rewrite any existing project. The domain validations
            (DoD/DoR/RF/rule/modules) are the SAME in both modes; the mode only
            changes who triggers it.
        executionMode:
          type: string
          enum:
            - container
            - mounted
            - runner
          example: container
          description: >-
            WHERE this project's command EXECUTES (RN-169/RN-421 — ADR
            0072/0104). `container` (default): the folder managed by the product
            inside PROJECT_WORKSPACES_ROOT, the usual behavior. `mounted`: a
            folder of YOURS, given in `workspacePath`, inside the
            installation-wide base `BRABO_PROJECTS_BASE` (ADR 0141) — creation
            validates only the path FORMAT plus that base, and the folder itself
            is created later, when Infra starts the container (RN-501, ADR
            0142). `runner`: a folder of YOURS that does NOT need a bind-mount —
            creation only validates the path format and the project is born
            "unverified"; run `brabo-runner --project <id> --dir <folder>` on
            your machine to confirm it (RN-423).
        workspacePath:
          type: string
          example: /home/you/projects/store
          description: >-
            The folder's ABSOLUTE path, required when `executionMode` is
            `mounted` or `runner`, and refused when it is `container`. In BOTH
            of them only the FORMAT is validated now — absolute, no `..`, not
            the system root, not a system folder, not overlapping the Brabo
            checkout (RN-422/RN-423). `mounted` adds one rule: it must sit
            inside `BRABO_PROJECTS_BASE`, the single folder of your machine the
            Brabo containers can see (ADR 0141) — with no base configured the
            mode is not available on this installation and creation says so.
            Disk is touched later: by the runner in `runner`, and when Infra
            starts the container in `mounted` (RN-501, ADR 0142).
        language:
          type: string
          example: pt-BR
          maxLength: 35
          description: >-
            The PROJECT's language (RN-619) — shared artifacts and turns with no
            human author use it. Any BCP-47 code the server recognizes, stored
            canonical; an unrecognized one is a 400. Omitted, the project starts
            with its creator's effective response language (RN-618: account
            choice > confirmed detection > interface language).
      required:
        - name
        - slug
    CreateSessionDto:
      type: object
      properties:
        kind:
          type: string
          enum:
            - consultiva
            - criativa
          example: criativa
          description: >-
            The session's intent. `consultiva` (consultative) is conversation
            only; `criativa` (creative) is the one that produces — it opens
            ideation with the Creative agent and is the only one that enters
            execution. REQUIRED: without it the response is 400, because the
            type is the opener's choice and gets recorded.
        name:
          type: string
          example: Cart checkout
          maxLength: 80
          description: >-
            Friendly name, optional. Does not replace the id's hashtag — screens
            compose the two. Blank counts as absent.
      required:
        - kind
    CreateSocketTicketDto:
      type: object
      properties:
        scope:
          type: string
          enum:
            - heartbeat
            - terminal
          example: heartbeat
          description: >-
            `heartbeat` requires the `viewer` role; `terminal` requires
            `developer` — the same minimum role as
            `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: >-
            Each id needs to reference an `artifact.business_rule` event that
            EXISTS — a made-up id is refused.
          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. Unique in the system and visible in the 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: >-
            The credential exists TODAY. `false` is historical spend from a key
            that was already removed — the consumption happened and does not
            disappear from the report because of that.
        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: >-
            Portion spent by AGENTS — the tab RN-058 started charging to the
            owner.
        costMicrosPessoas:
          type: number
          example: 350000
          description: >-
            Portion spent by PEOPLE in chat. Comes from the same key, and it is
            a different question.
        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: Owner of the keys — who funds this workspace's agents (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` — the provider accepted the credential. `recusado` — the
            provider rejected it (invalid/revoked key, no balance, network).
            `nao_suportado` — this provider has no verified test endpoint
            (`ollama`, `anthropic`, `openai`): NOTHING was verified, and saying
            "ok" here would be a lie.
        motivo:
          type: string
          example: 'connection test failed for openrouter: openrouter responded 401'
          description: Only on `recusado` — the reason the provider gave.
      required:
        - resultado
    CriterioDoAceiteResponseDto:
      type: object
      properties:
        regras:
          type: number
          example: 4
          description: Business rules in the project.
        cobertas:
          type: number
          example: 4
          description: Rules cited by at least one story — equal to `regras`.
        repositorio:
          type: string
          enum:
            - local
            - a_provisionar_local
          example: a_provisionar_local
          description: >-
            `local`: the project already had a `local` repository.
            `a_provisionar_local`: none yet — the accept provisions a `local`
            one.
      required:
        - regras
        - cobertas
        - repositorio
    DecideBootstrapPlanDto:
      type: object
      properties:
        planGeneratedAt:
          type: string
          example: '2026-08-01T23:45:00.000Z'
          format: date-time
          description: >-
            The `generatedAt` of the plan you SAW. Optimistic lock: if the plan
            has been regenerated since then (re-adoption, or the repository
            changed), the decision is refused with 409 instead of applying a yes
            given about something else.
      required:
        - planGeneratedAt
    DecideBootstrapPlanResponseDto:
      type: object
      properties:
        repository:
          $ref: '#/components/schemas/ProvisionedRepositoryResponseDto'
        bootstrap:
          description: >-
            Where the bootstrap ended up. On `skip` the cursor does NOT advance:
            no step ran, and the record says so.
          allOf:
            - $ref: '#/components/schemas/PassoDeBootstrapResponseDto'
      required:
        - repository
        - bootstrap
    DecideProjectImageInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
        image:
          type: string
          example: node:22-bookworm-slim
          description: >-
            OCI reference with an explicit TAG or digest. `latest` is REFUSED
            with 400 — the rule is domain-level.
        rationale:
          type: string
          example: >-
            The module_map is TypeScript on Node 22; the slim variant has the
            runtime and nothing else.
          description: Why this image. Minimum of 10 characters.
        network:
          type: string
          enum:
            - none
            - egress
          default: none
          description: >-
            The container's network posture. `none` is the default, and it is
            what makes "inside, the agent is free" a safe sentence.
        resources:
          type: object
          additionalProperties: true
          example:
            cpus: 2
            memoryMb: 4096
            pidsLimit: 512
          description: >-
            Resource cap. Above the maximum, the decision is REFUSED, never
            silently downgraded.
      required:
        - projectId
        - image
        - rationale
    DecisaoDeImagemResponseDto:
      type: object
      properties:
        image:
          type: string
          example: node:22-bookworm-slim
          description: >-
            OCI reference with an explicit TAG or digest. `latest` is refused:
            the artifact needs to say the same thing six months from now.
        rationale:
          type: string
          example: >-
            The module_map is all TypeScript over Node; the slim variant is
            enough and reduces surface.
          description: Why THIS image. This is what makes the decision reviewable.
        network:
          type: string
          enum:
            - none
            - egress
          example: none
          description: >-
            The container's network posture, decided ONCE in the artifact and
            not command by command. `egress` is cost and surface: the Architect
            asks, the user authorizes it at provisioning time.
        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: >-
            The WHOLE contract list: each call is a new version that replaces
            the previous one. What a module CONSUMES is not here — it is derived
            from the current module_map's `dependsOn` when read.
          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 when the area has no backlog task behind it (Infra, Phase 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: >-
            Stays in the event log alongside the denial. Optional, but
            recommended.
    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: >-
            Flagged by the Architect. Becomes an informative checklist in
            SecOps's verdict — there's no line-by-line correlation, and claiming
            there were would misrepresent the depth of the check.
      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: The question that was decided.
        decisao:
          type: string
          description: The user's decision.
        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 that apply: the cross-cutting ones (no module declared) plus
            the ones that cite this agent's module.
          type: array
          items:
            $ref: '#/components/schemas/DevContextAdrResponseDto'
        siblingTasks:
          description: >-
            The other tasks of the same story, with their status. The QA gate
            judges the delivery by what belongs to THIS task; a requirement of a
            sibling task is an observation, never a rejection (RN-765).
          type: array
          items:
            $ref: '#/components/schemas/DevContextSiblingTaskResponseDto'
        moduleOpenTasks:
          description: >-
            Tasks of OTHER stories of the same module that are not done yet,
            with the story title (at most 20). A requirement that depends on
            what another story will deliver is an observation for the QA gate,
            never a rejection (RN-785).
          type: array
          items:
            $ref: '#/components/schemas/DevContextModuleOpenTaskResponseDto'
        moduleOpenTasksTotal:
          type: number
          description: >-
            Real number of open tasks of the module in other stories — larger
            than `moduleOpenTasks.length` when the list was cut.
        moduleContract:
          nullable: true
          description: >-
            The current contract of the task module
            (`artifact.module_contracts`) — the source of the INTERFACE for the
            QA gate. When the story and the contract diverge, the verdict names
            the divergence instead of rejecting the dev that followed the
            contract (RN-786). `null` without a resolvable module or contract.
          type: object
          allOf:
            - $ref: '#/components/schemas/ContratoDeModuloResponseDto'
        primeiraDoModulo:
          type: boolean
          description: >-
            Whether this is the FIRST task of the module: the oldest
            `backlog.task_claimed` of the project for `module` is this task. The
            engine gives it twice the per-task budget (RN-774). `false` without
            `module`.
        decisoes:
          description: >-
            RN-859: the user's decisions tied to this task, its story or its
            rules by `afeta` — they win over RF/DoR on conflict. Newest first,
            at most 10, each text cut at 1,500 characters.
          type: array
          items:
            $ref: '#/components/schemas/DevContextDecisaoResponseDto'
        decisoesDoProjeto:
          description: >-
            RN-872 (ADR 0215): the project's OTHER decisions — those whose
            `afeta` does not touch this task. Separate section, inside the SAME
            cap of 10 as `decisoes` (task decisions take the cap first), newest
            first.
          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` is the initial state: the Architect has not generated
            one yet.
        diagrama:
          nullable: true
          description: The current diagram, or `null` while there is none.
          type: object
          allOf:
            - $ref: '#/components/schemas/C4DiagramaResponseDto'
        version:
          type: number
          example: 0
          description: >-
            Version of the current artifact; 0 when there is no diagram.
            Revising means generating again — history is never rewritten.
        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` is the initial state of every project and is what
            closes the RN-105 gate: no image means no container, and the Code
            tab doesn't open.
        decisao:
          nullable: true
          description: The current decision, or `null` when there is none yet.
          type: object
          allOf:
            - $ref: '#/components/schemas/DecisaoDeImagemResponseDto'
        version:
          type: number
          example: 0
          description: >-
            Version of the current artifact; 0 when there is no decision.
            Revising means issuing a new version — history is never rewritten.
        eventId:
          type: object
          nullable: true
          example: 01JC4Z0000EVENTO000000001
          description: Id of the event that fixed the current decision, for auditing.
        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: Execution session created.
        modules:
          example:
            - api
            - web
          description: >-
            Modules of the `module_map` that got a dev agent. One agent per
            module, each in an isolated worktree.
          type: array
          items:
            type: string
      required:
        - sessionId
        - modules
    FirstAccountInternalDto:
      type: object
      properties:
        email:
          type: string
          example: voce@exemplo.dev
          description: >-
            The owner e-mail, typed at the TTY. Normalized before anything is
            written; it is what the installer records in its marker, since an
            e-mail identifies and is not a secret.
        senha:
          type: string
          example: uma frase longa e minha
          description: >-
            Read without echo and confirmed by the installer. Used and discarded
            — never written to the `.env`, to the marker or to a log, and never
            generated by any code: there is no path where an account is born
            with a password the script chose. The minimum is the DOMAIN policy,
            the same code registration uses; the max length here is argon2id
            protection, not policy.
        nome:
          type: string
          example: Fulana de Tal
          description: >-
            Display name. Omitted: the personal workspace's name falls back to
            the e-mail local-part (RN-410) — nothing is invented.
      required:
        - email
        - senha
    FirstAccountInternalResponseDto:
      type: object
      properties:
        userId:
          type: string
          format: uuid
          example: 01JC4Z0000USUARIO0000000001
          description: The user that was created.
        email:
          type: string
          example: voce@exemplo.dev
          description: >-
            The NORMALIZED e-mail — what was actually written, which can differ
            from what was typed. This is what the installer records in its
            marker.
        workspaceId:
          type: string
          format: uuid
          example: 01JC4Z0000WORKSPACE00000001
          description: >-
            The personal workspace born in the SAME transaction (RN-410), with
            the account as its `owner`. Without it the install would close with
            a login that works and a dashboard where "New project" has nowhere
            to create.
      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: >-
            The decision is the user's — either a direct click, or delegated by
            a policy they wrote. Invariant across the four constitutionally
            manual gates (RN-071).
        status:
          type: string
          enum:
            - active
            - planned
        evidencia:
          type: object
          additionalProperties: true
          description: >-
            Where the proof that the gate passed lives: `event_log` (types +
            payload filter), `teste` or `ci` (target path). Absent on a
            `planned` gate.
        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: Flow the gate belongs to.
        dono:
          type: string
          example: area-qa
          description: Who judges.
        entrada:
          example:
            - pr-aberta
          description: What needs to exist for the gate to open.
          type: array
          items:
            type: string
        entregavel:
          type: object
          example: veredito-qa
          description: What the gate produces when it passes.
        aprovacaoHumana:
          type: boolean
          example: true
          description: >-
            The decision is the user's — direct on click OR delegated by a
            policy they wrote themselves (see 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: Version of the registry format.
        gates:
          description: >-
            Only ACTIVE gates. A `planned` gate describes a future role and
            should not appear on a screen that says what is happening right now.
          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: >-
            What happens next. `correct` returns to dev; `run_secops` advances
            the gate; `done` releases it to the user; `await_user` parks the
            cycle until the open question is answered (RN-860); `blocked` means
            the correction cap ran out and only a human can unblock it.
        task:
          $ref: '#/components/schemas/TaskResponseDto'
        openQuestionId:
          type: string
          format: uuid
          description: 'With `await_user`: the open question the cycle waits on.'
      required:
        - nextAction
        - task
    GitAuthorizeUrlResponseDto:
      type: object
      properties:
        authorizeUrl:
          type: string
          example: >-
            https://github.com/login/oauth/authorize?client_id=…&state=…&redirect_uri=…
          description: >-
            Where to send the browser. The `state` is signed by HMAC with
            `GIT_OAUTH_STATE_SECRET` — this is what prevents the callback from
            being forged.
      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 of the agent that passed the baton.
        toAgent:
          type: string
          example: po
          description: Slug of the receiving agent.
        artifactId:
          type: object
          example: 01JC4Z0000ARTEFATO000000001
          nullable: true
          description: Artifact that motivated the handoff (product_brief, module_map…).
        status:
          type: string
          enum:
            - offered
            - accepted
            - completed
            - rejected
            - superseded
          example: offered
          description: >-
            This field is MUTABLE — it is the current state. Each transition
            also becomes an immutable `handoff.*` event in the log, which is
            where the history lives. `superseded` (ADR 0182, RN-635): the offer
            stopped being the current one — the target agent was activated by
            another path, or a newer offer to the same target in the project
            replaced it (`handoff.superseded`). Only `offered` can be accepted.
        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: >-
            Only when `status=error`, with the failure message. A healthy
            response has no detail at all, on purpose: liveness is not the place
            to expose topology.
      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>/<model>` on the 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: >-
            The publisher is in the curated allowlist of known vendors
            (`domain/llm/huggingface-official-publishers.ts`). `false` is any
            third-party reupload — the screen shows the security warning when
            `includeCommunity=true` brought this result.
      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: >-
            Combined (RAG_SEARCH_WEIGHT_VECTOR*vector +
            RAG_SEARCH_WEIGHT_LEXICAL*lexical), already filtered by the
            threshold — every returned hit has passed it.
        vectorScore:
          type: number
          nullable: true
          description: Cosine similarity, 0..1. `null` when this chunk had no vector.
        lexicalScore:
          type: number
          nullable: true
          description: >-
            Normalized `ts_rank`, 0..1. `null` when the term did not match in
            this chunk.
        origin:
          type: object
          additionalProperties: true
          description: >-
            Discriminated union by `kind`: "file" (`sourcePath`/`headingPath`)
            or "session" (`sessionId`/`eventId`). See
            `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: Between 2 and 500 characters — same rule as the Code tab search.
        scopes:
          type: array
          description: When absent, searches all four scopes (RN-219/454).
          items:
            type: string
            enum:
              - docs
              - adr
              - session
              - local
        limit:
          type: number
          example: 10
          description: 1 to 10 — when absent, uses the cap.
      required:
        - query
    HybridSearchResponseDto:
      type: object
      properties:
        query:
          type: string
        searchId:
          type: string
          nullable: true
          format: uuid
          description: >-
            The `rag_searches` row this search left (RN-479) — the id a vote
            attaches to. `null` means the telemetry row was NOT written (the
            insert failed), which is not the same as "no results": there is
            nothing to attach a vote to, and the UI needs the two apart so it
            does not offer a control the api will refuse.
        hits:
          type: array
          items:
            $ref: '#/components/schemas/HybridSearchHitResponseDto'
        vectorAvailable:
          type: boolean
          description: >-
            `false` when the embedding provider did not respond — the search ran
            with only the lexical signal (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: Self-reported by the model, not measured.
        evidenceEventIds:
          example:
            - 01JC4Z8QK3M7YV2N5T9B0PXHRB
          description: >-
            At least one, and each one has to EXIST in this session. Made-up
            evidence is rejected with 400 — that is what separates a hypothesis
            from an opinion.
          type: array
          items:
            type: string
        terminationAnalysis:
          description: REQUIRED when `cause` indicates an abnormal termination.
          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: The analysis round that generated this hypothesis.
        agenteAlvo:
          type: string
          example: dev-api
          description: Which agent the hypothesis is about.
        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: >-
            Confidence declared by the model itself. Not a measurement, a
            self-report.
        evidenceEventIds:
          example:
            - 01JC4Z8QK3M7YV2N5T9B0PXHRB
          description: >-
            Log events that support the hypothesis. This is what makes the
            evidence auditable instead of taken on faith.
          type: array
          items:
            type: string
        terminationAnalysis:
          nullable: true
          description: Present only on hypotheses about an abnormally terminated session.
          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: >-
            The ORIGIN of the failure. Never deduced by elimination — lesson
            from ADR 0020.
        estadoDaSessao:
          type: string
          example: closed_abnormally
        analise:
          type: string
          example: The agent stopped responding after a 90s tool call.
      required:
        - causa
        - estadoDaSessao
        - analise
    IdiomaEfetivoResponseDto:
      type: object
      properties:
        language:
          type: string
          example: pt-BR
        origin:
          type: string
          enum:
            - sessao
            - conta
            - detectado
            - interface
          example: interface
      required:
        - language
        - origin
    ImagemDecididaResponseDto:
      type: object
      properties:
        decisao:
          $ref: '#/components/schemas/DecisaoDeImagemResponseDto'
        version:
          type: number
          example: 1
        containerDePe:
          type: object
          nullable: true
          example:
            status: running
            imageVersion: 2
          description: >-
            The `running`/`stopped` container registered for the project, or
            null. The new decision does NOT change it: it only applies once the
            container is recreated (stop, remove, start).
      required:
        - decisao
        - version
        - containerDePe
    ImagemParaOBrokerResponseDto:
      type: object
      properties:
        image:
          type: string
          example: node:22-bookworm-slim
          description: OCI reference with an explicit tag or digest.
        network:
          type: string
          enum:
            - none
            - egress
          example: none
        resources:
          $ref: '#/components/schemas/RecursosDoContainerResponseDto'
      required:
        - image
        - network
        - resources
    ImportLlmCredentialDto:
      type: object
      properties:
        provider:
          type: string
          enum:
            - anthropic
            - openai
            - openrouter
            - nvidia-nim
            - together
            - deepinfra
            - bitdeer
            - vultr
            - 9router
            - opencode-zen
          example: anthropic
        apiKey:
          type: string
          maxLength: 512
          description: >-
            The API key in plain text. Encrypted before touching the database;
            NEVER logged and NEVER returned.
        origem:
          type: string
          example: 'codex: ~/.codex/auth.json'
          maxLength: 200
          description: Tool and path the key was read from. Audit log only; never a value.
        substituir:
          type: boolean
          description: Must be `true` to replace an existing credential of this provider.
      required:
        - provider
        - apiKey
        - origem
    ImportLlmCredentialResponseDto:
      type: object
      properties:
        provider:
          type: string
          enum:
            - anthropic
            - openai
            - openrouter
            - nvidia-nim
            - together
            - deepinfra
            - bitdeer
            - vultr
            - 9router
            - opencode-zen
        substituida:
          type: boolean
          description: Whether an existing credential was replaced.
      required:
        - provider
        - substituida
    IndexDocsReportResponseDto:
      type: object
      properties:
        filesScanned:
          type: number
        docsChunks:
          type: number
        adrChunks:
          type: number
        truncated:
          type: boolean
        embedding:
          $ref: '#/components/schemas/IndexEmbeddingResponseDto'
      required:
        - filesScanned
        - docsChunks
        - adrChunks
        - truncated
        - embedding
    IndexEmbeddingResponseDto:
      type: object
      properties:
        available:
          type: boolean
        embedded:
          type: number
        skipped:
          type: number
        reason:
          type: string
      required:
        - available
        - embedded
        - skipped
    InfraArtifactResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z0000INFRAARTEFATO00001
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        sessionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
        title:
          type: string
          example: api's production Dockerfile
        prActionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRD
          description: >-
            Id of the `open_infra_pr` `proposed_action` that opened the PR. It
            is what the engine knows in return — there is no separate artifact
            id.
        gateStatus:
          type: string
          enum:
            - awaiting_qa
            - awaiting_secops
            - awaiting_user
          example: awaiting_qa
          description: Goes through the SAME QA and SecOps gates as dev PRs.
        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 when the project hasn't provisioned a repository yet. The
            Workflows subagent (Phase 8c) decides the CI pipeline format from
            this: "gitlab" generates .gitlab-ci.yml, any other value generates
            GitHub Actions.
        moduleRouting:
          description: >-
            The Architect-routed candidates (route_modules_to_infra, ADR 0131) —
            what the Infra Lead elects among when proposing container_start (ADR
            0130/0133). "sem_roteamento" when the Architect has not routed
            anything yet.
          allOf:
            - $ref: '#/components/schemas/EstadoDoRoteamentoResponseDto'
        projectImage:
          nullable: true
          description: >-
            RN-852: the CURRENT artifact.project_image decision (image, network,
            resources) — the FROM of the Dockerfile and the network the Infra
            compose respects. null when no image has been decided.
          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: >-
            The content comes from the `proposed_action`'s own payload: an infra
            artifact NEVER touches a worktree, same as 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: >-
            The patch `proposed_action` that produced this version, if there was
            one.
        sourceHypothesisId:
          type: object
          example: 01JC4Z0000HIPOTESE000000001
          nullable: true
          description: >-
            The Psychologist's hypothesis that motivated the patch — the closed
            loop.
        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: Whether this is the version in effect.
        diff:
          description: >-
            Diff of this version against the previous one, computed server-side
            so the UI does not need its own differ. The oldest one is diffed
            against empty, so it shows up as all-addition.
          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: A name for you to recognize this token later — not unique.
        expiresInDays:
          type: number
          example: 90
          description: >-
            Expires in N days. Omitted = no expiration (revocable at any time,
            but never expires on its own).
      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: Null = no expiration.
        revokedAt:
          type: object
          example: null
          nullable: true
        lastUsedAt:
          type: object
          example: null
          nullable: true
          description: Null = never used.
        token:
          type: string
          example: brb_9f8a...
          description: >-
            Store it now — it is not recoverable later. Use it in --token or
            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: >-
            How another module uses the item: call a function, hit a route,
            subscribe to an event, or build a data shape.
        assinatura:
          type: string
          example: 'placePiece(board: Board, piece: Piece, pos: Pos): Board'
          description: How another module uses it. Up to 300 characters.
        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: How many origin references the rule cites.
      required:
        - eventId
        - seq
        - actorId
        - titulo
        - descricao
        - origens
    JwksResponseDto:
      type: object
      properties:
        keys:
          description: Active public Ed25519 keys. Two during a rotation.
          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: The provider can LIST its own catalog (catalog sync).
        embeddings:
          type: boolean
          example: false
          description: Text → vector (ADR 0075). Only `true` when proven.
        routingPreference:
          type: boolean
          example: false
          description: >-
            The provider accepts a ROUTING PREFERENCE (`price`, `throughput`,
            `latency`) to pick among the upstreams serving the same model (ADR
            0166). Only `true` after a smoke against the real API returned the
            chosen upstream — reading the docs does not count.
        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: >-
            The provider accepts explicit PROMPT-CACHE breakpoints
            (`cache_control`) on the stable prefix of a call (RN-892). Only
            `true` after a smoke against the real API showed cached input tokens
            on a repeated call.
      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: >-
            The provider's failure. Comes in the BODY with 200, not as an HTTP
            error, because the accounting in `usage` remains valid — the turn
            spent, even while failing.
        errorCode:
          type: object
          example: null
          nullable: true
          description: >-
            The provider failure's normalized `code` (ADR 0041) — `auth`,
            `rate_limit`, `insufficient_credit` (HTTP 402, RN-730),
            `model_not_found`, `context_length`, `timeout`, `connection` or
            `upstream`. `null` with no error, or when the error is not the
            provider's (budget, missing binding). Decide by this, never by the
            text of `error`.
        modelName:
          type: object
          example: llama3.2:3b
          nullable: true
          description: >-
            Name of the model that generated the response (finding from problem
            2) — `null` when the turn failed before resolving a model (no
            binding, or binding to a nonexistent model).
        truncated:
          type: boolean
          enum:
            - true
          description: >-
            Present (always `true`) only when the provider cut the response at
            the output cap (`finish_reason: "length"` / `stop_reason:
            "max_tokens"`, RN-737): the text and the `toolCalls` arguments are
            truncated. The engine refuses to run a truncated tool call and
            narrates the cut.
        toolRouting:
          description: >-
            The step where the Jev chose the tool (ADR 0179, RN-625). Absent
            when the router was not consulted (provider other than OpenRouter,
            fewer than two tools, workspace switch off). The engine narrates it
            as `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` carries `text`; `done` closes with `usage`; `error` carries
            `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: >-
            Only on the `done`/`final` frame — name of the model that generated
            the response (finding from problem 2). `null` when the turn failed
            before resolving a model.
        truncated:
          type: boolean
          enum:
            - true
          description: >-
            Only on the `final` frame, and only when the provider cut the
            response at the output cap (RN-737).
      required:
        - type
    LlmUsageResponseDto:
      type: object
      properties:
        inputTokens:
          type: number
          example: 1820
        outputTokens:
          type: number
          example: 340
        costMicros:
          type: number
          example: 52700
          description: >-
            Cost in micro-USD: the REAL cost the provider returned when it did
            (OpenRouter's `usage.cost`), otherwise the frozen catalog price (ADR
            0188, RN-665).
        estimated:
          type: boolean
          example: false
          description: >-
            `true` when the provider did not return a count and the cost was
            ESTIMATED. The number is worth less, and whoever consumes it needs
            to be able to say so.
      required:
        - inputTokens
        - outputTokens
        - costMicros
        - estimated
    LocalFolderFileDto:
      type: object
      properties:
        path:
          type: string
          example: src/domain/rag/chunking.ts
          description: Relative path within the chosen folder — never absolute, never `..`.
        content:
          type: string
          description: Plain UTF-8 text — never base64.
      required:
        - path
        - content
    LocalizacaoDoProjetoResponseDto:
      type: object
      properties:
        tipo:
          type: string
          enum:
            - gerenciada
            - montada
            - indisponivel
          example: gerenciada
          description: >-
            Which of the broker's roots resolves this folder. `gerenciada` →
            `PROJECT_WORKSPACES_HOST_ROOT` (the product-managed folder, named by
            `workspaceDirName`); `montada` → `BRABO_PROJECTS_HOST_BASE` (the
            single base of Mounted projects, ADR 0141); `indisponivel` → no root
            of this server reaches it, and `motivo` says why. Three states, not
            two: `runner` projects and legacy `mounted` projects created outside
            the base are refusals with DIFFERENT fixes, and collapsing them into
            a null would make the caller guess which one it got.
        segmento:
          type: string
          example: loja
          description: >-
            The part the broker's root does NOT cover, joined to it as
            `<root>/<segmento>`. For `gerenciada` it is `workspaceDirName`; for
            `montada`, the RELATIVE path under the base (it may contain `/`).
            ABSENT — not null — when `tipo` is `indisponivel`: the two variants
            are a discriminated union on the api side and the wire says the same
            thing, so a caller that reads `segmento` without checking `tipo`
            breaks loudly instead of composing `<root>/null`.
        motivo:
          type: string
          example: o projeto está no modo "runner"…
          description: >-
            Why no root reaches this folder. Present only when `tipo` is
            `indisponivel`. It is a message meant to be repeated back to whoever
            operates, so it names the mode or the 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: >-
            A name for a human to recognize this MACHINE later, in the device
            key list of every project it serves. Not unique. The installer
            suggests the hostname; nothing here is derived from it by the api.
        publicKeyJwk:
          type: string
          example: '{"kty":"OKP","crv":"Ed25519","x":"…"}'
          description: >-
            The PUBLIC Ed25519 JWK (RFC 8037), serialized as JSON. The pair is
            generated on the machine and the private half never travels — a JWK
            carrying `d` is refused with 400 saying so, never stored.
      required:
        - name
        - publicKeyJwk
    MachineDeviceKeyInternalResponseDto:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: 01JC4Z0000CHAVE000000000001
          description: >-
            The registration id. Write it into the PRIVATE JWK as `kid` before
            saving the file (RN-475): it is the only link between the key on
            disk and the public half on the server, and every step downstream
            only passes it along.
        userId:
          type: string
          format: uuid
          example: 01JC4Z0000USUARIO0000000001
          description: >-
            The owner the api RESOLVED — the installation's sole user. The
            caller does not choose it and does not send it; this field is here
            so the installer can check it against the account it just created
            rather than assume.
        name:
          type: string
          example: servidor-de-casa
        createdAt:
          type: string
          example: '2026-09-12T12:00:00.000Z'
          format: date-time
        replacedKeyIds:
          example: []
          description: >-
            Ids of the MACHINE keys this one REPLACED — revoked in the same
            transaction. Empty is the normal case (a fresh installation).
            Non-empty means a local agent still holding one of them stops
            getting tickets, and the installer should say so instead of letting
            it be discovered. Project keys (ADR 0118) are never touched.
          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: Only the agent that claimed the task can move it.
        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: Always `true`; failure becomes an HTTP error.
        mensagemId:
          type: string
          example: 01JC4Z0000EVENTO000000000001
          description: >-
            Id of the `chat.message` event recorded for this message — the id
            that cancels it while it waits in the queue.
        entrega:
          type: string
          enum:
            - lida
            - enfileirada
          example: enfileirada
          description: >-
            `lida`: the agent started a turn with it right away. `enfileirada`:
            the agent was mid-turn, so the message joined its queue and will be
            read, together with any others queued, in ONE turn when the current
            one ends (RN-673). The session log carries `chat.message_queued` /
            `chat.message_delivered` / `chat.message_cancelled`.
        posicao:
          type: number
          example: 2
          description: >-
            Position in the queue (1 = next). Present only when `entrega` is
            `enfileirada`.
      required:
        - ok
        - mensagemId
        - entrega
    MirrorSyncResultInternalDto:
      type: object
      properties:
        ok:
          type: boolean
          example: true
          description: >-
            The REAL outcome of the copy, reported by the local agent after it
            finished — never an optimistic "ok" before the round ends.
        destination:
          type: string
          example: /home/you/mirrors/store
          description: >-
            The destination the round actually resolved (success) or tried to
            use (failure), on the USER machine. FROZEN in the row: showing the
            project's current destination next to yesterday's copy would be the
            screen asserting about a folder that round never touched. Omitted
            when the failure is about the destination itself.
        filesCopied:
          type: number
          example: 412
          description: >-
            Regular files copied in this round. `0` is a number, not an absence
            — "synced and copied nothing" is a state of its own (RN-088).
        filesSkipped:
          type: number
          example: 3
          description: >-
            Entries git listed that are not regular files — a nested repository
            (a dev agent's worktree is one), a symlink, or a file that vanished
            between the listing and the copy.
        filesRefused:
          type: number
          example: 0
          description: >-
            Targets the per-file guard refused (a symlink escaping the
            destination). Counted, never swallowed.
        error:
          type: string
          example: >-
            o espelho não conseguiu listar o trabalho com o git: not a git
            repository
          description: >-
            The NAMED failure message. Stored truncated when very long, saying
            it was truncated. It never erases the last successful sync — which
            row is CURRENT is decided by comparing the two timestamps.
      required:
        - ok
    MirrorSyncResultResponseDto:
      type: object
      properties:
        recorded:
          type: boolean
          example: true
        status:
          type: string
          example: synced
          enum:
            - never
            - synced
            - failed
          description: >-
            Which of the three states is CURRENT after this write. `never` is
            unreachable here (every write stamps one of the two timestamps) and
            is listed because the vocabulary is the same one the screen reads.
      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 of the workspace, project, or session the binding is pinned to.
            In the `agent` and `area` scopes, which exist PER PROJECT, it is
            composite: `<projectId>:<agent slug|area key>` (ADR 0064).
        modelId:
          type: string
          example: 01JC4Z0000MODELO00000000001
        routingPreference:
          type: string
          enum:
            - price
            - throughput
            - latency
          nullable: true
          example: null
          description: >-
            How a hub picks the upstream for this binding (ADR 0166, RN-583).
            `null` = the hub decides on its own. Only ever set for a provider
            that declares the `routingPreference` capability.
        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: Identifier at the provider.
        displayName:
          type: string
          example: Claude Opus 4.8
        inputPricePerMillionMicros:
          type: number
          example: 15000000
          description: Price per million INPUT tokens, in micro-USD.
        outputPricePerMillionMicros:
          type: number
          example: 75000000
          description: Price per million OUTPUT tokens, in micro-USD.
        contextWindow:
          type: object
          example: 200000
          nullable: true
          description: Also the model's `context_length` capability.
        supportsToolCalling:
          type: boolean
          example: true
          description: >-
            NATIVE tool calling. Without this the model is chat-only and cannot
            be bound to an agent (RN-040).
        supportsStreaming:
          type: boolean
          example: true
        supportsVision:
          type: boolean
          example: false
          description: >-
            Accepts IMAGE input. `false` means the provider did not declare the
            modality — not that the model can't do it (ADR 0041).
        supportsReasoning:
          type: boolean
          example: true
          description: >-
            Accepts explicit reasoning (thinking). On OpenRouter it comes from
            `supported_parameters: reasoning`.
        generatesImage:
          type: boolean
          example: false
          description: >-
            PRODUCES image — an axis different from accepting it as input. Few
            models have it, and confusing the two sends the user to the wrong
            model.
        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: >-
            Price typed in from the provider's docs, not synced. The price sync
            does not overwrite a row marked without an explicit decision.
        availability:
          type: string
          enum:
            - available
            - unavailable
          example: available
          description: >-
            REMOTE reality observed by sync, an axis independent from curation.
            `unavailable` is a model that vanished from the provider catalog —
            it is never deleted, because bindings and cost history point to it.
        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: Last time sync saw the model in the provider catalog.
        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: >-
            Curation by the OWNER **of this workspace**: a deactivated model
            disappears from the selector but stays in historical costs. A model
            discovered by sync has no curation row, and no row means `false`.
        uses:
          type: array
          example:
            - codigo
          description: >-
            What THIS workspace uses the model for. The operator's opinion, not
            a provider capability — no catalog publishes "good for code". An
            empty list means "nobody has an opinion", not "not fit for it".
          items:
            type: string
            enum:
              - codigo
              - documentacao
              - analise
              - imagem
              - conversa
        freeRoutingAlias:
          type: boolean
          example: false
          description: >-
            OpenRouter free-routing alias (an id starting with `~`): the catalog
            price is a showcase price and the bill comes from whichever upstream
            served the call. Curation refuses to ACTIVATE it (422
            `alias_de_roteamento_livre`); one activated before the rule stays
            active, marked by this flag, and once deactivated it cannot come
            back. Derived from provider and name on read, never stored.
      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: Identifier at the provider.
        displayName:
          type: string
          example: Claude Opus 4.8
        inputPricePerMillionMicros:
          type: number
          example: 15000000
          description: Price per million INPUT tokens, in micro-USD.
        outputPricePerMillionMicros:
          type: number
          example: 75000000
          description: Price per million OUTPUT tokens, in micro-USD.
        contextWindow:
          type: object
          example: 200000
          nullable: true
          description: Also the model's `context_length` capability.
        supportsToolCalling:
          type: boolean
          example: true
          description: >-
            NATIVE tool calling. Without this the model is chat-only and cannot
            be bound to an agent (RN-040).
        supportsStreaming:
          type: boolean
          example: true
        supportsVision:
          type: boolean
          example: false
          description: >-
            Accepts IMAGE input. `false` means the provider did not declare the
            modality — not that the model can't do it (ADR 0041).
        supportsReasoning:
          type: boolean
          example: true
          description: >-
            Accepts explicit reasoning (thinking). On OpenRouter it comes from
            `supported_parameters: reasoning`.
        generatesImage:
          type: boolean
          example: false
          description: >-
            PRODUCES image — an axis different from accepting it as input. Few
            models have it, and confusing the two sends the user to the wrong
            model.
        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: >-
            Price typed in from the provider's docs, not synced. The price sync
            does not overwrite a row marked without an explicit decision.
        availability:
          type: string
          enum:
            - available
            - unavailable
          example: available
          description: >-
            REMOTE reality observed by sync, an axis independent from curation.
            `unavailable` is a model that vanished from the provider catalog —
            it is never deleted, because bindings and cost history point to it.
        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: Last time sync saw the model in the provider catalog.
        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: >-
            Curation by the OWNER **of this workspace**: a deactivated model
            disappears from the selector but stays in historical costs. A model
            discovered by sync has no curation row, and no row means `false`.
        uses:
          type: array
          example:
            - codigo
          description: >-
            What THIS workspace uses the model for. The operator's opinion, not
            a provider capability — no catalog publishes "good for code". An
            empty list means "nobody has an opinion", not "not fit for it".
          items:
            type: string
            enum:
              - codigo
              - documentacao
              - analise
              - imagem
              - conversa
        freeRoutingAlias:
          type: boolean
          example: false
          description: >-
            OpenRouter free-routing alias (an id starting with `~`): the catalog
            price is a showcase price and the bill comes from whichever upstream
            served the call. Curation refuses to ACTIVATE it (422
            `alias_de_roteamento_livre`); one activated before the rule stays
            active, marked by this flag, and once deactivated it cannot come
            back. Derived from provider and name on read, never stored.
        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 per 1M, before.
        inputAfterMicros:
          type: number
          example: 3000000
          description: Micro-USD per 1M, after.
        outputBeforeMicros:
          type: number
          example: 10000000
        outputAfterMicros:
          type: number
          example: 12000000
        source:
          type: string
          enum:
            - manual
            - sync
          example: manual
          description: >-
            `manual` was someone typing from the provider's docs; `sync` came
            from the remote catalog.
        changedBy:
          type: object
          example: 01JC4Z0000USUARIO0000000001
          nullable: true
          description: '`null` when it came from sync — no person behind it.'
        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: >-
            Present only when the Hub published `usedStorage` in the search —
            never guessed.
        status:
          type: string
          enum:
            - pending_confirmation
            - confirmed
            - pulling
            - active
            - failed
          example: pending_confirmation
          description: >-
            `pending_confirmation` → `confirmed` → `pulling` → `active` |
            `failed`. The first two states ARE the second explicit confirmation
            the product requires before any download runs.
        confirmedAt:
          type: object
          example: null
          nullable: true
          format: date-time
          description: When the confirmation click happened.
        failedReason:
          type: object
          example: null
          nullable: true
          description: >-
            Only in `failed`, prefixed by the origin (infra | model | code |
            policy) — never a silent failure.
        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: Identifier at the provider.
        displayName:
          type: string
          example: Claude Opus 4.8
        inputPricePerMillionMicros:
          type: number
          example: 15000000
          description: Price per million INPUT tokens, in micro-USD.
        outputPricePerMillionMicros:
          type: number
          example: 75000000
          description: Price per million OUTPUT tokens, in micro-USD.
        contextWindow:
          type: object
          example: 200000
          nullable: true
          description: Also the model's `context_length` capability.
        supportsToolCalling:
          type: boolean
          example: true
          description: >-
            NATIVE tool calling. Without this the model is chat-only and cannot
            be bound to an agent (RN-040).
        supportsStreaming:
          type: boolean
          example: true
        supportsVision:
          type: boolean
          example: false
          description: >-
            Accepts IMAGE input. `false` means the provider did not declare the
            modality — not that the model can't do it (ADR 0041).
        supportsReasoning:
          type: boolean
          example: true
          description: >-
            Accepts explicit reasoning (thinking). On OpenRouter it comes from
            `supported_parameters: reasoning`.
        generatesImage:
          type: boolean
          example: false
          description: >-
            PRODUCES image — an axis different from accepting it as input. Few
            models have it, and confusing the two sends the user to the wrong
            model.
        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: >-
            Price typed in from the provider's docs, not synced. The price sync
            does not overwrite a row marked without an explicit decision.
        availability:
          type: string
          enum:
            - available
            - unavailable
          example: available
          description: >-
            REMOTE reality observed by sync, an axis independent from curation.
            `unavailable` is a model that vanished from the provider catalog —
            it is never deleted, because bindings and cost history point to it.
        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: Last time sync saw the model in the provider catalog.
        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: >-
            History is immutable: each new map is one more version, and the
            current one is the one with the highest `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: Names of other modules. A cycle here gets the map REJECTED on write.
          type: array
          items:
            type: string
        resources:
          description: >-
            What THIS module needs alone inside the project container (RN-683,
            ADR 0199). Absent when the Architect did not declare it. The Infra
            starts the container with the SUM over modules, floored at the
            default while any module is undeclared.
          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: >-
            The report's actor — always whoever called. Another actor's row does
            not get in.
        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: The daemon's own state, with no translation and no collapse.
        imagem:
          type: string
          example: node:22-bookworm-slim
        iniciadoEm:
          type: object
          nullable: true
          format: date-time
          description: '`null` when the container has never started.'
      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 of the agent that passed the baton.
        toAgent:
          type: string
          example: po
          description: Slug of the receiving agent.
        artifactId:
          type: object
          example: 01JC4Z0000ARTEFATO000000001
          nullable: true
          description: Artifact that motivated the handoff (product_brief, module_map…).
        status:
          type: string
          enum:
            - offered
            - accepted
            - completed
            - rejected
            - superseded
          example: offered
          description: >-
            This field is MUTABLE — it is the current state. Each transition
            also becomes an immutable `handoff.*` event in the log, which is
            where the history lives. `superseded` (ADR 0182, RN-635): the offer
            stopped being the current one — the target agent was activated by
            another path, or a newer offer to the same target in the project
            replaced it (`handoff.superseded`). Only `offered` can be accepted.
        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`: a new offer. `substituiu_oferta`: a new offer, and the
            pending one(s) to the same target became `superseded`.
            `ja_oferecido`: no new row — the pending offer already there is
            returned (same session and no new artifact, or `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 of the agent that passed the baton.
        toAgent:
          type: string
          example: po
          description: Slug of the receiving agent.
        artifactId:
          type: object
          example: 01JC4Z0000ARTEFATO000000001
          nullable: true
          description: Artifact that motivated the handoff (product_brief, module_map…).
        status:
          type: string
          enum:
            - offered
            - accepted
            - completed
            - rejected
            - superseded
          example: offered
          description: >-
            This field is MUTABLE — it is the current state. Each transition
            also becomes an immutable `handoff.*` event in the log, which is
            where the history lives. `superseded` (ADR 0182, RN-635): the offer
            stopped being the current one — the target agent was activated by
            another path, or a newer offer to the same target in the project
            replaced it (`handoff.superseded`). Only `offered` can be accepted.
        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`: a new offer. `substituiu_oferta`: a new offer, and the
            pending one(s) to the same target became `superseded`.
            `ja_oferecido`: no new row — the pending offer already there is
            returned (same session and no new artifact, or `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: Always `true`; failure becomes an HTTP error.
      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: The agent that asked.
        criadaEm:
          type: string
          format: date-time
        status:
          type: string
          enum:
            - open
            - answered
        autorDaSessao:
          type: string
          nullable: true
          description: >-
            The user who created the origin session — the only one who may
            answer.
        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 matching the filter; `itens` is capped at 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: Pass as `afterSeq` on the next page; `null` means end of list.
      required:
        - items
        - nextCursor
    PaginaDeEventosResponseDto:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/SessionEventResponseDto'
        nextCursor:
          type: object
          example: 41
          nullable: true
          description: Pass as `afterSeq` for the next page. `null` means end of the 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`: the agent started, within the lead's cap.
            `aguardando_autorizacao`: the request became a `proposed_action` and
            NOTHING started until you decide. `recusado`: the area has an
            invalid `max_parallel`.
        actionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
          description: The action to decide on. Only in `aguardando_autorizacao`.
        ativosNaSessao:
          type: number
          example: 2
          description: Dev agents already up IN THE SESSION, summed across all modules.
        maxParallel:
          type: number
          example: 2
          description: The cap the lead uses without asking.
        motivo:
          type: string
          description: Why the request was refused. Only in `recusado`.
      required:
        - estado
    PerfilApagadoResponseDto:
      type: object
      properties:
        deleted:
          type: number
          example: 2
          description: How many profiles were deleted.
        optedOut:
          type: boolean
          example: true
          description: >-
            Always `true`: deleting WITHOUT recording the opt-out would be
            cosmetic, because the next round would re-derive everything.
      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: Automatically approved patterns.
          type: array
          items:
            type: string
        deny:
          example:
            - Terminal(rm -rf *)
            - Terminal(git push --force*)
          description: >-
            Forbidden patterns. `deny` wins over `allow` and over the agent's
            autonomy — no configuration combination releases what is listed
            here.
          type: array
          items:
            type: string
        ask:
          example:
            - Terminal(pnpm add *)
          description: Patterns that always require a human decision.
          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: Null = no expiration.
        revokedAt:
          type: object
          example: null
          nullable: true
        lastUsedAt:
          type: object
          example: null
          nullable: true
          description: Null = never used.
        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: Null = no expiration.
        revokedAt:
          type: object
          example: null
          nullable: true
        lastUsedAt:
          type: object
          example: null
          nullable: true
          description: Null = never used.
      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` on the first stage — there is no "conversion from" anything.'
      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: >-
            Simple average in ms — `null` with no session having both a commit
            AND a merge.
      required:
        - perSession
        - averageMs
    ProductMetricsProjectResponseDto:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        name:
          type: string
          example: exp003
      required:
        - id
        - name
    ProductMetricsResponseDto:
      type: object
      properties:
        project:
          $ref: '#/components/schemas/ProductMetricsProjectResponseDto'
        totalActionsConsidered:
          type: number
          example: 18
          description: Git actions considered (git_commit/pr_open/git_merge, any 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: >-
            Has to be in the catalog returned by `anamnese-context` — a made-up
            competency is REJECTED. The catalog depends on the `module_map`,
            which is why the validation is a domain one, not a DTO one.
        level:
          type: string
          enum:
            - iniciante
            - intermediario
            - avancado
          example: avancado
        rationale:
          type: string
          example: Fixed three generic typing errors without help.
        evidenceEventIds:
          example:
            - 01JC4Z8QK3M7YV2N5T9B0PXHRB
          description: >-
            Each id has to be from a session event OF THIS project — citing an
            event from another project is refused.
          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: >-
            The "whys" behind the level. Without this the profile would be a
            verdict with no appeal.
        evidenceEventIds:
          example:
            - 01JC4Z8QK3M7YV2N5T9B0PXHRB
          description: Log events that support the level.
          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` when whoever has the profile is no longer a project member —
            the profile survives the member's removal.
      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: >-
            The `artifact.business_rule` EVENT's id. This is the value that goes
            into `business_rule_ids` when creating the story — there is no rules
            table.
        title:
          type: string
          example: Cart accepts at most 50 items
        description:
          type: string
          example: Above that, the addition is refused with 409.
          description: >-
            The rule's content, not just the statement: the story's requirement
            comes from it. This is the field that distinguishes this reading
            from the screen's coverage.
        coveredByStoryIds:
          example:
            - 01JC4Z0000HISTORIA000000001
          description: Stories that already cite this rule.
          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: >-
            How many rules no story covers. This is the PO's pending item, and
            the number that the `listar_regras_de_negocio` tool puts in front of
            the model.
      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` when the project doesn''t have a provisioned repository yet.'
        provisioningStatus:
          type: string
          enum:
            - provisioning
            - provisioned
            - provision_failed
            - awaiting_plan_decision
          nullable: true
          example: provisioned
          description: '`null` when the bootstrap never started. Derived from the cursor.'
        budget:
          nullable: true
          description: >-
            `null` when the project NEVER had a budget set — distinct from a
            zeroed-out row, and this is what makes the card offer "Set budget".
          type: object
          allOf:
            - $ref: '#/components/schemas/ProjectCardBudgetResponseDto'
        latestSessionId:
          type: object
          example: 01JC4Z0000SESSAO00000000001
          nullable: true
        latestSeq:
          type: number
          example: 41
          description: >-
            Last `seq` already recorded in the most recent session (`nextSeq -
            1`); 0 when there is no session. The web compares this against what
            has already been seen to count unread.
        lastEvent:
          nullable: true
          description: The most recent session's last event — the card's footer line.
          type: object
          allOf:
            - $ref: '#/components/schemas/SessionEventResponseDto'
        storiesAwaitingPromotion:
          type: number
          example: 2
          description: >-
            Stories the PO finished and that are awaiting the user's promotion
            (RN-048).
        pendingApprovalsCount:
          type: number
          example: 8
          description: >-
            `proposed_actions` with `status = pending` across the WHOLE project,
            all sessions — not just the most recent one. This is what the
            sidebar shows as the project's badge (RN-151).
        onlineAgentCount:
          type: number
          example: 2
          description: >-
            How many agents are ONLINE right now — working or with a pending
            item awaiting a decision (RN-409). Never team size: an
            `idle`/`idle_tripped` dev agent does not count, an `idle`
            conversational agent does not count, QA/SecOps never count (single
            verdict per invocation).
        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: >-
            The mounted-projects base of this installation
            (`BRABO_PROJECTS_BASE`, ADR 0141) — the same value `GET
            .../projects-base` returns, repeated here so a client can render the
            breadcrumb without a second call. `null` means the installation
            offers no Mounted mode.
        path:
          type: object
          example: /home/voce/brabo/clientes
          nullable: true
          description: >-
            The folder actually listed, normalized. Equals `base` when the
            request omitted `path`. `null` only when there is no base at all:
            nothing was listed because there is nowhere to list.
        entries:
          example:
            - api
            - loja
            - website
          description: >-
            The SUBDIRECTORY names directly under `path`, sorted, never
            recursive. Files, symlinks and dot-prefixed entries are excluded —
            see `arquivos`, `simbolicos` and `truncado`.
          type: array
          items:
            type: string
        truncado:
          type: boolean
          example: false
          description: >-
            The folder holds more than 500 subdirectories and only the first 500
            came back. Sorting happens BEFORE the cut, so the cut is
            deterministic instead of "whatever the filesystem returned first".
        arquivos:
          type: number
          example: 12
          description: >-
            How many non-directory entries were left out. A folder full of code
            must not look empty (RN-180).
        simbolicos:
          type: number
          example: 1
          description: >-
            How many symlinks were left out. The browser never follows one — a
            link is reported, never descended into, so a link pointing outside
            the base is not a way out of it.
      required:
        - base
        - path
        - entries
        - truncado
        - arquivos
        - simbolicos
    ProjectGitRemoteResponseDto:
      type: object
      properties:
        kind:
          type: string
          example: remote
          enum:
            - local
            - remote
          description: >-
            `local` is a bare-repo path on disk with no credential; `remote` is
            a URL that requires authentication.
        origin:
          type: string
          example: https://github.com/daneiel/hello-api.git
          description: >-
            What becomes `git remote add origin <this>`. **No credential
            embedded**, by design: this is the value that gets written into the
            workspace's `.git/config`, readable by anyone with access to the
            project's directory.
        defaultBranch:
          type: string
          example: main
        token:
          type: string
          nullable: true
          description: >-
            Only on `remote`. The workspace OWNER's token (RN-058), decrypted at
            call time. Whoever receives it injects it per invocation and
            discards it.
        username:
          type: string
          nullable: true
          example: x-access-token
          description: User of the HTTP Basic pair; the token is the password.
      required:
        - kind
        - origin
        - defaultBranch
    ProjectMemberComUsuarioResponseDto:
      type: object
      properties:
        userId:
          type: string
          example: 01JC4Z0000USUARIO0000000002
        role:
          type: string
          enum:
            - viewer
            - developer
            - maintainer
            - owner
          example: maintainer
          description: >-
            EFFECTIVE role: the project role when there is one, otherwise the
            one inherited from the workspace (RN-471) — the project role
            OVERRIDES the workspace one in both directions, higher or lower.
        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: >-
            Linear hierarchy: owner > maintainer > developer > viewer. Each role
            includes the permissions of the ones after it.
        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: >-
            The destination declared TODAY (`projects.mirror_path`, RN-515).
            `null` means the project has no mirror — the NORMAL state, and what
            makes the screen hide this line entirely instead of inventing an
            absence.
        status:
          type: string
          example: synced
          enum:
            - never
            - synced
            - failed
          description: >-
            `never` — no runner has reported a round yet (there is no row).
            `synced` — the last round copied; `filesCopied` may be `0`, which is
            "looked and there was nothing to copy" and has a sentence of its
            own. `failed` — the last round failed, and the error is newer than
            the last success. The API derives this from the two timestamps so
            the rule has one source; the screen still gets the raw fields to
            write the sentence.
        lastSyncedAt:
          type: object
          example: '2026-09-07T12:04:00.000Z'
          nullable: true
          description: >-
            The last SUCCESSFUL sync. A later failure never erases it — it is
            the most useful thing this screen has while the mirror is broken.
        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: >-
            Where the last round actually wrote — FROZEN. It diverges from
            `mirrorPath` after someone changes the destination, because the
            grant travels in the join and only changes when the runner
            reconnects (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: Unique within the workspace.
        workspaceDirName:
          type: string
          example: checkout-3f2b1c8e
          description: >-
            Name of this project's physical workspace folder in
            PROJECT_WORKSPACES_ROOT (RN-109). `<slug>-<8 id chars>` on a new
            project; the raw UUID on a project created before this column
            existed. Frozen at creation — editing `slug` later doesn't recompute
            it.
        executionMode:
          type: string
          enum:
            - container
            - mounted
            - runner
          example: container
          description: >-
            WHERE the command executes (RN-169/RN-421 — ADR 0072/0104).
            `container`: the folder managed in PROJECT_WORKSPACES_ROOT.
            `mounted`: the user's folder in `workspacePath`, inside
            `BRABO_PROJECTS_BASE` and reached through the base bind-mount (ADR
            0141). `runner`: the user's folder confirmed by the runner. Both
            `mounted` and `runner` report the confirmation in
            `workspaceVerifiedAt`.
        workspacePath:
          type: object
          example: null
          nullable: true
          description: >-
            The user's folder absolute path — filled in for `mounted`/`runner`,
            always `null` in `container` mode.
        workspaceVerifiedAt:
          type: object
          example: null
          nullable: true
          description: >-
            When the path was confirmed on disk. `null` = not yet verified. In
            `runner` the confirmation comes from the CLI connecting (RN-423); in
            `mounted` it is stamped when Infra starts the container and the
            folder is materialized (RN-501, ADR 0142). `container` never fills
            this field in. It records A confirmation, never a heartbeat.
        mirrorPath:
          type: object
          example: null
          nullable: true
          description: >-
            The mirror destination on the USER'S machine — the folder OUTSIDE
            the mounted base where the local agent copies the work to (RN-515,
            ADR 0147). `null` means the project has no mirror, and that is the
            NORMAL state, never an error. Only `mounted`/`runner` can have one.
            Set through `PUT /projects/:projectId/mirror-path`; it rides along
            every project read so nothing needs a dedicated endpoint to learn
            it. The value was validated LEXICALLY only — whether the folder
            exists is known by the local agent, not by this API.
        language:
          type: string
          example: pt-BR
          description: >-
            The PROJECT's language (RN-619): the language of everything with no
            human author — shared artifacts (product brief, business rules,
            ADRs) and turns nobody typed (kickoff, dev agents, gates, commit
            messages, PR bodies). A canonical BCP-47 code, open list. A new
            project starts with its creator's effective response language
            (RN-618). Set through `PATCH /projects/:projectId` (`maintainer`).
            The engine sends it to the model as an ephemeral system message at
            the end of every LLM call of a turn with no human author (RN-622);
            an artifact emitted during a person's turn still follows that
            person's response language.
        createdBy:
          type: string
          example: 01JC4Z0000USUARIO0000000001
        taskBudgetMicros:
          type: object
          example: 500000
          nullable: true
          description: >-
            Token cap per dev agent task, in micro-USD. `null` uses the domain
            default.
        maxConsecutiveBlocked:
          type: object
          example: 3
          nullable: true
          description: >-
            Circuit breaker per dev agent (Phase 12b — RN-047): consecutive
            tasks ending blocked before stopping at idle_tripped. `null` uses
            the domain default.
        storyPromotion:
          type: string
          enum:
            - manual
            - auto
          example: auto
          description: >-
            Who promotes a story to `ready` (Phase 12c — RN-048). `manual`: the
            PO proposes and the user decides. `auto`: automatic promotion on
            creation — the new-project default since RN-659; projects created
            before it keep whatever value they had.
        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: >-
            The single folder on the operator's computer that the Brabo
            containers can see, mounted by IDENTITY (`$X:$X`) into `api` and
            `engine` (ADR 0141). `null` means `BRABO_PROJECTS_BASE` is not set:
            the installation offers no Mounted mode, and the project wizard must
            not offer it. Never a failure — absent is a normal state.
        brokerConfigurado:
          type: boolean
          example: false
          description: >-
            Whether this installation has a container broker configured
            (`BROKER_URL` set, ADR 0130). `container` and `mounted` projects
            only start a container through the broker (ADR 0144), and without
            one every dev agent is blocked forever (ADR 0143). With `false` the
            project wizard does not pre-select Mounted, keeps Container and
            Mounted visible but not selectable with the reason in text, and
            pre-selects Runner (ADR 0161, RN-573). It says the variable exists,
            never that the broker answers. Installation configuration, like
            `projectsBase`: the same for every 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: >-
            The project's MOST RECENT session — the same one `projects-summary`
            reports in `latestSessionId`.
        events:
          description: >-
            In DESCENDING `seq` order — the first item is the most recent
            (RN-100). At most 50 per project, the same cap `GET .../events`
            applies without `limit`, and when there are more unread than that
            the ones returned are the NEWEST. How many were left out comes from
            `latestSeq` minus the cutoff, without another request.
          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: >-
            Readable reason. The story may have lost its readiness, or one of
            its modules may have dropped out of `module_map` between the
            proposal and the decision.
      required:
        - storyId
        - reason
    PromoteStoriesDto:
      type: object
      properties:
        storyIds:
          example:
            - 01JC2XK9Q7
            - 01JC2XKB4M
          description: >-
            Ids of the stories to promote to `ready`. A single one is a batch of
            1. Each story is validated and promoted on its own — one that fails
            does not block the others.
          type: array
          items:
            type: string
      required:
        - storyIds
    PromoteStoriesResponseDto:
      type: object
      properties:
        promoted:
          description: Ids that went to `ready`, with their tasks already pickable.
          type: array
          items:
            type: string
        failed:
          description: Empty when the whole batch passed.
          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: >-
            Whether this is the current version of the template. Always `true`
            right after an upsert that created a new version; can come back
            `false` when the idempotency hit (same hash) points to a version
            that a LATER publish already deactivated.
      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: >-
            Action type. Each type has its own minimum role: `git_push`,
            `git_merge`, and `git_branch_protect` require `maintainer`, the rest
            `developer`.
        actor:
          description: Who is proposing it.
          allOf:
            - $ref: '#/components/schemas/ActorDto'
        payload:
          type: object
          additionalProperties: true
          example:
            command: pnpm test
          description: Action parameters, with a shape specific to the `actionType`.
      required:
        - actionType
        - actor
        - payload
    ProposedActionResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRC
          description: ULID of the action.
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        sessionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
        seq:
          type: number
          example: 7
          description: >-
            Order of the action within the session; used as a cursor in
            listings.
        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: >-
            What the action would do. `git_push` and `git_merge` require
            `maintainer`.
        payload:
          type: object
          additionalProperties: true
          example:
            command: pnpm test
          description: Parameters of the action, specific to the `actionType`.
        status:
          type: string
          enum:
            - pending
            - approved
            - denied
            - auto_approved
            - executed
            - failed
          example: pending
          description: >-
            State in the pipeline. `auto_approved` is distinct from `approved`
            on purpose: the log needs to distinguish what a person decided from
            what the policy released.
        resolvedPolicy:
          type: string
          enum:
            - auto_approve
            - require_approval
            - deny
          example: require_approval
          description: >-
            What `permissions.json` decided for this action. `deny` ALWAYS wins
            over `allow` — not even the agent's autonomy overrides it.
        actor:
          description: Who proposed it.
          allOf:
            - $ref: '#/components/schemas/ActorResponseDto'
        decidedBy:
          type: object
          example: null
          nullable: true
          description: Id of the user who approved or denied it. Never an agent.
        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: >-
            Execution result, with a shape specific to each `actionType` —
            output and exit code for terminal, PR number and URL for the git
            types. `null` while the action has not been executed.
        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: The two tiers use genuinely different models; the cost diverges.
        triggeredBy:
          type: string
          enum:
            - auto
            - manual
          example: auto
        eventCount:
          type: number
          example: 128
          description: Size of the analyzed log.
        cause:
          type: string
          enum:
            - normal
            - timeout
            - kill
            - crash
            - node_shutdown
            - unknown
          example: timeout
          description: >-
            Cause CLASSIFIED by the engine. It, not the terminal status, decides
            whether `terminationAnalysis` is required. Optional so as not to
            break an older engine during a rolling deploy.
        hypotheses:
          type: array
          items:
            $ref: '#/components/schemas/HypothesisDraftDto'
      required:
        - projectId
        - tier
        - triggeredBy
        - eventCount
        - hypotheses
    ProposeHypothesesResponseDto:
      type: object
      properties:
        analysisId:
          type: string
          example: 01JC4Z0000ANALISE0000000001
          description: The created round. This session's previous one becomes superseded.
        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: The WHOLE file as it would end up, not a diff.
        rationale:
          type: string
          example: The task was reopened three times for lack of a criterion.
        hypothesisId:
          type: string
          format: uuid
          example: 01JC4Z0000HIPOTESE000000001
          description: >-
            The hypothesis that motivated the patch. This is what closes the
            Anamnesis loop.
      required:
        - projectId
        - agent
        - proposedContent
        - rationale
    ProposeMaxParallelInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        area:
          type: string
          example: dev
          description: The area whose cap would go up.
        proposto:
          type: number
          example: 4
          minimum: 1
          description: >-
            The proposed cap. Has to be HIGHER than the current one — proposing
            the same or less turns into noise in a queue the user needs to read,
            and the Anamnesis runs periodically.
        rationale:
          type: string
          example: >-
            You authorized the same request four times in this window, and none
            was denied.
          description: Anchored in OBSERVED DECISIONS, not an impression.
      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: The repository's id on the provider.
        url:
          type: string
          example: https://github.com/acme/checkout
        defaultBranch:
          type: string
          example: main
          description: >-
            The project's policy uses `dev`, `qa`, and `main` as permanent
            branches.
        visibility:
          type: string
          enum:
            - public
            - private
          example: private
        origin:
          type: string
          enum:
            - created
            - adopted
          example: created
          description: >-
            `created` = Brabo created the repository; `adopted` = it pointed to
            one that already existed. Immutable once recorded (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: Repository name on the provider.
        visibility:
          type: string
          enum:
            - public
            - private
          example: private
        namespace:
          type: string
          example: acme
          description: >-
            Organization or group. When omitted, the repository is created in
            whoever registered the credential's personal account.
      required:
        - name
        - visibility
    ProvisionRepositoryResponseDto:
      type: object
      properties:
        repository:
          $ref: '#/components/schemas/ProvisionedRepositoryResponseDto'
        bootstrap:
          description: >-
            Where the Gitflow bootstrap stopped. Work continues in the
            background — track it via `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: >-
            Triage tier. The two use GENUINELY different models — this is what
            makes the cost actually diverge instead of just the label.
        triggeredBy:
          type: string
          enum:
            - auto
            - manual
          example: auto
          description: '`manual` is the reanalysis requested by a human.'
        supersedes:
          type: object
          example: null
          nullable: true
          description: Id of the analysis this one replaces.
        superseded:
          type: boolean
          example: false
        supersededAt:
          type: object
          example: null
          format: date-time
          nullable: true
        eventCountAtAnalysis:
          type: number
          example: 128
          description: Log size at the time of the analysis — the snapshot it saw.
        createdAt:
          type: string
          example: '2026-07-26T18:00:00.000Z'
          format: date-time
        costMicros:
          type: number
          example: 4200
          description: >-
            Real cost in micro-USD, summed from the round's `token_usage`. There
            is only a row here for a COMPLETED run: a failed run does not record
            one, on purpose.
        hypothesisCount:
          type: number
          example: 3
          description: How many hypotheses the round yielded.
      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: >-
            A CURRENT analysis already exists for this session. This is what
            makes the automatic path idempotent: with `true` the worker
            short-circuits without spending anything.
        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: >-
            What was already proposed before, so the round doesn't repeat
            itself.
          type: array
          items:
            $ref: '#/components/schemas/PriorHypothesisResponseDto'
      required:
        - alreadyAnalyzed
        - sessionStatus
        - terminationReason
        - businessRules
        - priorHypotheses
    PsychologistStatusResponseDto:
      type: object
      properties:
        enabled:
          type: boolean
          example: false
          description: >-
            Whether a NEW analysis round can run today, automatic or on-demand.
            Disabled does not touch existing data — past analyses and hypotheses
            stay intact and visible.
      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: Agent slug — the voter.
      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: The 1-based position the judged chunk held in THAT search.
      required:
        - searchId
        - chunkId
        - verdict
        - rank
    RagFeedbackRequestDto:
      type: object
      properties:
        searchId:
          type: string
          format: uuid
          description: >-
            The `searchId` returned by the search that produced this hit — never
            invented by the client.
        chunkId:
          type: string
          format: uuid
          description: >-
            The chunk being judged. Must be one of the hits THAT search
            returned, otherwise 400.
        verdict:
          type: string
          enum:
            - util
            - irrelevante
          description: >-
            Two values, not a 1-5 scale: a finer scale invites per-voter
            differences in ruler that no aggregation recovers later.
      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: >-
            The 1-based position the judged chunk held in THAT search — the
            number that separates "the index is poor" from "the weights are
            wrong".
      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: >-
            Real `MAX(chunks.created_at)` for this scope — never a guessed "N
            minutes ago" (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: >-
            The session the agent is running in. Absent means the caller had no
            session — the telemetry row is still written (RN-479), only the
            `rag.search` timeline narration is skipped. Never invented by the
            api: a session it did not receive is a session that did not exist.
        agent:
          type: string
          example: dev-lead
          description: >-
            Agent slug, recorded as the telemetry actor (`actor_kind: agent`).
            Absent falls back to the `system` actor rather than inventing a
            user.
      required:
        - projectId
        - query
    RagSearchInternalHitResponseDto:
      type: object
      properties:
        chunkId:
          type: string
          format: uuid
          description: >-
            The chunk id — half of the reference a `rag_feedback` vote needs
            (RN-480). Without it the agent could read a hit and have no way to
            say it was useful.
        path:
          type: string
          example: docs/adr/0080-chat-rag-pipeline-indexacao.md
          description: >-
            File path (`docs`/`adr` scopes) or `session:<id>` (`session` scope,
            no real file path).
        chunk:
          type: string
          description: The full content of the retrieved chunk.
        score:
          type: number
          description: >-
            Combined score (vector + lexical), already filtered by the
            threshold.
        excerpt:
          type: string
          description: >-
            Short preview of the chunk, for display without blowing up the
            model's context.
      required:
        - chunkId
        - path
        - chunk
        - score
        - excerpt
    RagSearchInternalResponseDto:
      type: object
      properties:
        searchId:
          type: string
          nullable: true
          format: uuid
          description: >-
            The `rag_searches` row this search left (RN-479) — the other half of
            the vote reference. `null` when the telemetry row was not written;
            the tool then omits the ids instead of offering the model a
            reference that would be refused.
        hits:
          type: array
          items:
            $ref: '#/components/schemas/RagSearchInternalHitResponseDto'
        degraded:
          type: boolean
          description: >-
            `true` when the QUERY embedding was not available and the search
            fell back to lexical-only (same semantics as `vectorAvailable:
            false` from `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: >-
            Since `protect_branches` is the LAST step, acknowledging its failure
            closes the bootstrap — the project becomes reachable from the
            dashboard.
      required:
        - status
    RecordDelegationInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        area:
          type: string
          example: qa
          description: >-
            The area that delegated — "qa" (Phase 8b) or "infra" (Phase 8c).
            Text, not enum.
        taskId:
          type: string
          format: uuid
          example: 01JC4Z0000TASK00000000001
          description: >-
            The backlog task behind the delegation, when it exists. QA (Phase
            8b) always sends it; Infra (Phase 8c) never does — the delegation is
            about the SESSION, not a task.
        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 of the `artifact.qa_verdict` session_event for THIS
            subspecialty's VERDICT — required when `status` is "completed".
        failureOrigin:
          type: string
          enum:
            - infra
            - modelo
            - codigo
            - politica
          example: infra
          description: >-
            The ORIGIN of the failure (ADR 0020/0038), never by elimination —
            required when `status` is "failed".
        failureReason:
          type: string
          example: iteration limit reached without emit_perf_seguranca_verdict
        justification:
          type: string
          example: story with no relevant performance NFR
          description: >-
            Why the lead chose NOT to delegate — required when `status` is
            "dispensed". Never left blank: dispensing is a recorded decision,
            not silence (CLAUDE.md, Phase 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` (QA only, RN-860): the cycle waits for the
            user's answer to an open question — it does not count as a
            correction.
        resumo:
          type: string
          example: Missing tests for the insufficient-stock path.
        itens:
          example:
            - No test for a nonexistent SKU
          description: What needs to change, item by item.
          type: array
          items:
            type: string
        pergunta:
          type: string
          example: Should a nonexistent code answer 404?
          description: 'With `needs_user_decision`: the new open question (up to 500 chars).'
        sugestao:
          type: string
          example: 404 with a plain message.
          description: 'With `needs_user_decision`: the agent''s suggestion.'
        openQuestionId:
          type: string
          format: uuid
          description: >-
            With `needs_user_decision`: an EXISTING open question instead of a
            new one.
        maxCorrections:
          type: number
          example: 3
          description: >-
            Cap on round trips before the task becomes `blocked`. Omitted uses
            the domain's default.
      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: The `open_infra_pr` `proposed_action` that opened the 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: The profiles recorded in this round.
      required:
        - runId
        - profiles
    RecursosDoContainerResponseDto:
      type: object
      properties:
        cpus:
          type: number
          example: 2
          description: CPUs, fractional value allowed.
        memoryMb:
          type: number
          example: 4096
          description: Memory in MiB.
        pidsLimit:
          type: number
          example: 512
          description: >-
            Process cap. This is what contains a fork bomb without depending on
            a verb allowlist.
      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: The account's email.
        senha:
          type: string
          example: correct horse battery staple
          minLength: 12
          description: >-
            Password. Minimum of 12 characters; no requirement for uppercase,
            digit, or symbol.
        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. Encrypted by envelope encryption before
            touching the database and never returned, not even encrypted.
            Registration does NOT test the token (ADR 0050) — to verify it use
            `POST /users/me/credentials/{provider}/test`. The 512-character cap
            is protection against an absurd payload, not format validation.
      required:
        - provider
        - token
    RegistroDeContainerResponseDto:
      type: object
      properties:
        status:
          type: string
          enum:
            - provisioning
            - running
            - stopped
            - failed
            - removed
          example: running
          description: What was RECORDED (project_containers.status).
        imageVersion:
          type: number
          example: 1
        imagem:
          type: object
          nullable: true
          example: node:22-bookworm-slim
          description: >-
            The image FROZEN at `imageVersion`, resolved from the
            `artifact.project_image` event at that exact version — never the
            current one, which may have been revised since. `null` when that
            version's event could not be found.
        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: >-
            The new name. `null` (or a blank string) CLEARS the name and the
            session goes back to identifying itself only by the hashtag — this
            is the undo path, which is why the field accepts null instead of
            requiring a removal route.
      required:
        - name
    RepoBootstrapStatusResponseDto:
      type: object
      properties:
        status:
          type: string
          enum:
            - provisioning
            - provisioned
            - provision_failed
            - awaiting_plan_decision
          example: provisioning
          nullable: true
          description: >-
            `null` when there was never a provisioning in this project.
            `awaiting_plan_decision` is an ADOPTED repository with a generated
            plan not yet decided: nothing runs until someone approves the plan
            or adopts it as is.
        sessionId:
          type: object
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
          nullable: true
          description: >-
            The bootstrap's DEDICATED session. The progress screen reads its
            `bootstrap.step_*` events to build the live checklist.
        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: >-
            How many times the bootstrap has already been attempted. It is
            idempotent and resumable: repeating doesn't duplicate what already
            worked.
      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` is NOT terminal: it means "I am releasing this session".
            If another replica adopts it, it stays alive; if no one adopts it
            before the timeout, the drain sends `closed_abnormally`. `created`
            and `active` are left out because entering them is a decision made
            by the api, never by the engine reporting a termination.
        reason:
          type: string
          example: heartbeat_timeout
          description: The ORIGIN of the termination, not a diagnosis by elimination.
      required:
        - projectId
        - to
    RequestManualHandoffDto:
      type: object
      properties:
        toAgent:
          type: string
          example: staff
          description: >-
            Slug of the target agent. Must be a lead or an area-less agent —
            never a subagent.
      required:
        - toAgent
    RequestModelPullDto:
      type: object
      properties:
        repoId:
          type: string
          example: meta-llama/Llama-3.1-8B-Instruct-GGUF
          description: '`<publisher>/<model>` on the Hugging Face Hub.'
        estimatedSizeBytes:
          type: number
          example: 4900000000
          description: >-
            Comes from the search when the Hub published the size — never
            guessed by the client.
      required:
        - repoId
    RequestPasswordResetDto:
      type: object
      properties:
        email:
          type: string
          example: fulano@brabo.dev
      required:
        - email
    ResetPasswordDto:
      type: object
      properties:
        token:
          type: string
          description: Single-use token received by email.
        novaSenha:
          type: string
          example: another pretty long passphrase
          minLength: 12
      required:
        - token
        - novaSenha
    ResolvedBindingResponseDto:
      type: object
      properties:
        modelId:
          type: string
          example: 01JC4Z0000MODELO00000000001
        origin:
          type: string
          enum:
            - workspace
            - project
            - area
            - agent
            - session
          example: project
          description: >-
            Which scope the value came from. The cascade is session → agent →
            project → workspace, and exposing the ORIGIN is what lets the UI say
            "inherited from the project" instead of showing a value with no
            provenance.
        routingPreference:
          type: string
          enum:
            - price
            - throughput
            - latency
          nullable: true
          example: null
          description: >-
            The routing preference OF THE BINDING THAT WON the cascade (ADR
            0166). It never cascades on its own: a skipped level takes its
            preference with it, and no level inherits only the preference of
            another.
        skipped:
          description: >-
            More specific scopes the cascade discarded before reaching `origin`.
            Empty on the normal path.
          type: array
          items:
            $ref: '#/components/schemas/SkippedBindingResponseDto'
        herdadoDoLead:
          type: string
          example: secops
          description: >-
            Present when the agent asked about had no model of its own (nor of
            its area or project) and inherited the RESOLVED binding of its area
            lead (RN-703). `origin` is then the origin of the lead binding.
      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: >-
            Models that did not exist in the database. They enter INACTIVE —
            activating is the owner's curation (RN-043).
        reencontrados:
          type: number
          example: 1
          description: >-
            Models that were `unavailable` and came back to the catalog. Their
            `isActive` is left untouched: the owner's choice survives the
            absence.
        indisponibilizados:
          type: number
          example: 0
          description: >-
            Models that disappeared from the remote catalog. They become
            `unavailable` and are NEVER deleted — bindings and cost history
            point to them.
        pulado:
          type: string
          enum:
            - sem_capability
            - sem_credencial
            - falha
          description: >-
            Present when the provider was not synced. A skipped provider does
            not mark anything unavailable: "I don't know what's there" is not
            "there's nothing there".
        origemDaFalha:
          type: string
          enum:
            - infra
            - modelo
          description: >-
            Only with `pulado: "falha"`. Origin vocabulary from ADR 0020 —
            `infra` when the provider was never even reached, `modelo` when it
            responded refusing.
        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: >-
            The reason is REQUIRED: it becomes a message pinned to the PO
            session, and a return with no reason hands back work with no
            information.
      required:
        - reason
    RollbackResponseDto:
      type: object
      properties:
        agent:
          type: string
          example: dev-api
        restoredFrom:
          type: number
          example: 2
          description: The version whose content was restored.
        toVersion:
          type: number
          example: 5
          description: >-
            The NEW version the rollback created. History is immutable:
            restoring deletes nothing, it adds.
        cacheInvalidated:
          type: boolean
          example: true
          description: Whether the engine's instruction cache was successfully invalidated.
      required:
        - agent
        - restoredFrom
        - toVersion
        - cacheInvalidated
    RosterFactsResponseDto:
      type: object
      properties:
        executionActivated:
          type: boolean
          example: true
          description: >-
            The most recent session has already recorded `execution.activated` —
            this is what brings per-module dev agents into the roster.
        moduleNames:
          example:
            - api
            - web
          description: >-
            Module names from the CURRENT module_map (highest `version`). One
            dev agent per module, once execution has been activated.
          type: array
          items:
            type: string
        gatesEverOpened:
          type: boolean
          example: true
          description: >-
            Some PR gate (dev or infra) has opened AT LEAST ONCE in this session
            — this is what brings QA and SecOps into the roster. Covers the
            whole session, not a window of the last N events.
        delegatedSubagents:
          example:
            - qa-automacao
          description: >-
            Subagents with at least one delegation recorded in the session,
            whatever the outcome — dismissal is a recorded decision, not
            silence.
          type: array
          items:
            type: string
        activatedAgents:
          example:
            - arquiteto
            - infra
          description: >-
            Agents with at least one `agent.activated` in the session, most
            recently activated first (by `seq`), one entry per agent (RN-630).
            Covers the WHOLE session, not the 200-event window: the client sums
            it to the window and trusts it only when `latestSessionId` is the
            session it is reading.
          type: array
          items:
            type: string
        infraActive:
          type: boolean
          example: false
          description: An `accepted` handoff to `infra` exists in the most recent session.
        uxDesignerActive:
          type: boolean
          example: false
          description: >-
            An `accepted` handoff to `ux-designer` exists in the most recent
            session (ADR 0087).
        staffActive:
          type: boolean
          example: false
          description: >-
            An `accepted` handoff to `staff` exists in the most recent session
            (docs/fluxo.yml, ADR 0088) — dormant for automatic triggering, only
            reflects an already-accepted MANUAL activation.
      required:
        - executionActivated
        - moduleNames
        - gatesEverOpened
        - delegatedSubagents
        - activatedAgents
        - infraActive
        - uxDesignerActive
        - staffActive
    RoteamentoDeModuloInternalDto:
      type: object
      properties:
        modulo:
          type: string
          example: checkout-api
          description: Module name — must exist in the current module_map.
        imagemCandidata:
          type: string
          example: node:22-bookworm-slim
          description: >-
            OCI reference with an explicit TAG or digest for this module.
            `latest` is REFUSED with 400 — the rule is domain-level.
        porque:
          type: string
          example: >-
            This module is TypeScript on Node 22; the slim variant has the
            runtime and nothing else.
          description: Why THIS image for THIS module. Minimum of 10 characters.
      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: >-
            One item per module of the current module_map. The candidate image
            is the Architect proposing, not deciding — Infra elects among the
            candidates in a later step.
          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: The PR commits a `.env.example` to copy before running.
        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: Attributes the cost to this agent in the team panel.
        messages:
          type: array
          items:
            type: object
            additionalProperties: true
          example:
            - role: user
              content: Read file X.
          description: History already assembled by the engine's ContextManager.
        tools:
          type: array
          items:
            type: object
            additionalProperties: true
          description: Tools offered to the model; `parameters` is JSON Schema.
        catalogoCompleto:
          type: boolean
          example: true
          description: >-
            Asks for the WHOLE tool catalog: the Jev tool router (ADR 0179) is
            skipped for this call. The engine sets it when it repeats a step
            whose restricted menu made the model answer without calling a tool.
      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: >-
            Null = MACHINE key (ADR 0154): valid for any project of its owner,
            not only this one. See `especie`.
        especie:
          type: string
          enum:
            - projeto
            - maquina
          example: projeto
          description: >-
            `projeto` = bound to the project of this route (ADR 0118). `maquina`
            = describes the MACHINE (ADR 0154) and appears in the listing of
            every project it serves — revoking it drops the local agent in all
            of them.
        createdAt:
          type: string
          example: '2026-08-27T12:00:00.000Z'
          format: date-time
        revokedAt:
          type: object
          example: null
          nullable: true
          description: Null = active. A revoked key keeps appearing in the list.
        lastUsedAt:
          type: object
          example: null
          nullable: true
          description: >-
            Null = never used — the sign of an orphan key (tab closed in the
            middle of the runner automatic setup flow).
      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: >-
            The project folder name (RN-109) — a relative segment under the
            machine base, never an absolute path.
        workspaceVerifiedAt:
          type: object
          example: null
          nullable: true
          description: >-
            When the runner first confirmed the folder (RN-423). Null = never
            confirmed. It records a confirmation, not a heartbeat (RN-468): it
            does not say the folder is up now.
      required:
        - projectId
        - name
        - workspaceDirName
        - workspaceVerifiedAt
    RunnerTicketResponseDto:
      type: object
      properties:
        ticket:
          type: string
          description: >-
            Opaque single-use token, base64url. 30s TTL: gone after the first
            successful `connect/3` on the `/runner` socket, or when it expires.
        expiresAt:
          type: string
          example: '2026-08-09T12:00:30.000Z'
          description: When the ticket expires (ISO 8601).
        engineWsUrl:
          type: string
          example: ws://localhost:4000/runner
          description: >-
            Public URL of the Phoenix `/runner` socket, already in ws(s):// —
            ready for the client (runner CLI or web) to open the connection with
            `{ticket}` in the 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: >-
            Title of the rule JUST appended. Rules with the same normalized
            title (RN-080 makes it the new one) are left out of the comparison.
      required:
        - projectId
        - kind
        - title
    SemanticDuplicateCheckResponseDto:
      type: object
      properties:
        status:
          type: string
          enum:
            - warned
            - clean
            - skipped
            - nothing_to_compare
          description: >-
            `warned` NEVER blocks: the item was already written. `skipped`
            carries the reason — no embedding provider, daemon down, the 10 s
            ceiling — and is also narrated in the 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: Cosine threshold — a STARTING POINT, not calibrated (ADR 0198).
        compared:
          type: number
          example: 12
          description: How many existing items were compared (the most recent, up to 100).
        total:
          type: number
          example: 12
          description: How many exist in the project.
        reason:
          type: string
          example: provider "ollama" did not answer
        message:
          type: string
          nullable: true
          description: >-
            The sentence the agent reads in the tool result; `null` when there
            is nothing to say.
      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: User's message to the session's active agent.
      required:
        - text
    SendChatMessageDto:
      type: object
      properties:
        text:
          type: string
          example: Summarize what we have already decided about checkout.
          description: The user's message. The response comes back as an SSE stream.
      required:
        - text
    SessaoResponseDto:
      type: object
      properties:
        accessToken:
          type: string
          description: Short-lived EdDSA JWT.
        expiresIn:
          type: number
          example: 900
          description: Seconds until the access token expires.
        locale:
          type: string
          example: pt-BR
          enum:
            - pt-BR
            - en
            - ru
            - zh-Hans
          description: >-
            The user's language preference (i18n foundation, Wave 6a). It comes
            here — and not in a separate call — so the web app never needs an
            extra round-trip just to know which language to render the screen
            in.
      required:
        - accessToken
        - expiresIn
        - locale
    SessionAgentTokenUsageResponseDto:
      type: object
      properties:
        actorId:
          type: string
          example: dev-api
          description: The agent's slug, or the user's id.
        costMicros:
          type: number
          example: 3400000
          description: Cost in 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: >-
            The requested session plus the execution session(s) its approved Dev
            Lead plan activated (RN-828).
          type: array
          items:
            type: string
        regras:
          type: number
          example: 11
          description: '`artifact.business_rule` in the whole session.'
        decisoes:
          type: number
          example: 6
          description: '`artifact.decision_record` in the whole session.'
        itensDeRegras:
          description: >-
            Latest business rules of the session, capped at 50; the total is
            `regras` (RN-844).
          type: array
          items:
            $ref: '#/components/schemas/ItemDeRegraResponseDto'
        itensDeDecisoes:
          description: >-
            Latest decision records of the session, capped at 50; the total is
            `decisoes` (RN-844).
          type: array
          items:
            $ref: '#/components/schemas/ItemDeDecisaoResponseDto'
        artefatosPorAgente:
          type: array
          items:
            $ref: '#/components/schemas/ArtefatosDoAgenteResponseDto'
        arquivosTocados:
          description: >-
            Files of the PRs opened in this session and in the linked execution
            session (read from the PR diff, RN-844), then files from
            `git_commit`/`git_push` payloads.
          allOf:
            - $ref: '#/components/schemas/ArquivosTocadosResponseDto'
      required:
        - sessoesLidas
        - regras
        - decisoes
        - itensDeRegras
        - itensDeDecisoes
        - artefatosPorAgente
        - arquivosTocados
    SessionEventResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRB
          description: The event's ULID.
        sessionId:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
        seq:
          type: number
          example: 41
          description: Order within the session. Monotonic and gapless.
        type:
          type: string
          example: chat.message
          description: Event type. The full inventory is in `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: Free-form shape, specific to each `type`. See the events reference.
        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: The session's ULID.
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        createdBy:
          type: string
          example: 01JC4Z0000USUARIO0000000001
          description: Who opened the session.
        status:
          type: string
          enum:
            - created
            - active
            - closing
            - closed
            - closed_abnormally
          example: active
          description: >-
            Explicit state machine: created → active → closing → closed |
            closed_abnormally. An invalid transition responds 409.
        kind:
          type: string
          enum:
            - consultiva
            - criativa
          example: criativa
          description: >-
            The INTENT with which the session was opened, chosen at creation and
            immutable. `consultiva` (consultative) is conversation only;
            `criativa` (creative) produces and is the only one that enters
            execution. Not to be confused with execution state, which remains
            the `execution.activated` event in the log.
        name:
          type: object
          example: Cart checkout
          nullable: true
          description: >-
            Friendly name, or `null`. Screens compose it with the id's hashtag;
            it never replaces it.
        nextSeq:
          type: number
          example: 42
          description: >-
            Next `seq` of the event log. Serves as a cursor: `?afterSeq=41`
            fetches everything after 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: Only filled in terminal states.
        terminationReason:
          type: object
          example: null
          nullable: true
          description: >-
            Reason reported by the engine when terminating (heartbeat_timeout,
            conversation_idle_timeout, killed, exception…). `null` on a human
            close or a still-live session.
        traceParent:
          type: object
          example: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
          nullable: true
          description: >-
            W3C `traceparent` of the root span. This is how the whole session is
            recovered in Tempo.
        technical:
          type: boolean
          example: false
          description: >-
            Whether this is the TECHNICAL session opened by repository
            provisioning (the one linked from `repo_bootstraps`), as opposed to
            a work session. It is the link, never the name: renaming the session
            does not change it. Screens that pick "the most recent session" skip
            it unless it is the only one.
      required:
        - id
        - projectId
        - createdBy
        - status
        - kind
        - name
        - nextSeq
        - createdAt
        - updatedAt
        - closedAt
        - terminationReason
        - traceParent
        - technical
    SessionPendingWorkResponseDto:
      type: object
      properties:
        pending:
          type: boolean
          example: true
          description: >-
            There is work that blocks closing due to tab inactivity: an
            `offered` handoff, a `pending` action, an agent mid-turn, a dev
            agent working or blocked, or a conversational agent waiting for the
            user (RN-064, RN-581).
        motivo:
          type: object
          example: handoff po → arquiteto aguardando aceite
          nullable: true
          description: What is hanging. `null` when there's nothing.
        aguardandoUsuarioDesde:
          type: string
          example: '2026-09-18T12:00:00.000Z'
          nullable: true
          description: >-
            Set ONLY when the one thing pending is a conversational agent
            waiting for the user (RN-581): the instant its turn ended. It is the
            only pending signal with a ceiling, and the engine applies it
            (`SESSION_CONVERSATION_IDLE_TIMEOUT_MS`, default 8h) — past it the
            session closes with `conversation_idle_timeout`. `null` otherwise.
      required:
        - pending
        - motivo
        - aguardandoUsuarioDesde
    SessionResponseDto:
      type: object
      properties:
        id:
          type: string
          example: 01JC4Z8QK3M7YV2N5T9B0PXHRA
          description: The session's ULID.
        projectId:
          type: string
          example: 01JC4Z0000PROJETO0000000001
        createdBy:
          type: string
          example: 01JC4Z0000USUARIO0000000001
          description: Who opened the session.
        status:
          type: string
          enum:
            - created
            - active
            - closing
            - closed
            - closed_abnormally
          example: active
          description: >-
            Explicit state machine: created → active → closing → closed |
            closed_abnormally. An invalid transition responds 409.
        kind:
          type: string
          enum:
            - consultiva
            - criativa
          example: criativa
          description: >-
            The INTENT with which the session was opened, chosen at creation and
            immutable. `consultiva` (consultative) is conversation only;
            `criativa` (creative) produces and is the only one that enters
            execution. Not to be confused with execution state, which remains
            the `execution.activated` event in the log.
        name:
          type: object
          example: Cart checkout
          nullable: true
          description: >-
            Friendly name, or `null`. Screens compose it with the id's hashtag;
            it never replaces it.
        nextSeq:
          type: number
          example: 42
          description: >-
            Next `seq` of the event log. Serves as a cursor: `?afterSeq=41`
            fetches everything after 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: Only filled in terminal states.
        terminationReason:
          type: object
          example: null
          nullable: true
          description: >-
            Reason reported by the engine when terminating (heartbeat_timeout,
            conversation_idle_timeout, killed, exception…). `null` on a human
            close or a still-live session.
        traceParent:
          type: object
          example: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
          nullable: true
          description: >-
            W3C `traceparent` of the root span. This is how the whole session is
            recovered in 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: The effective language.
        origin:
          type: string
          enum:
            - sessao
            - conta
            - detectado
            - interface
          example: interface
          description: >-
            Where `language` came from: `sessao` (fixed in this session) >
            `conta` (Account choice) > `detectado` (detected AND confirmed) >
            `interface` (the interface language).
        sessionOverride:
          type: string
          nullable: true
          example: null
        account:
          type: string
          example: automatico
          description: '`automatico` or the code chosen on the Account.'
        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: >-
            The language YOUR recent messages point to, when the screen should
            ASK whether to use it for the answers (RN-624) — `null` means no
            question. Detection never changes the preference by itself: only
            answering `confirm` on `POST
            /users/me/preferences/detected-language` does. Never asked when the
            effective language comes from an explicit choice (this session or
            the Account), when it already is the detected one, or when you
            declined this language before. Best effort: a detection failure is
            `null`, never an error of this route.
      required:
        - language
        - origin
        - sessionOverride
        - account
        - detected
        - interfaceLocale
        - detectionQuestion
    SetAgentAutonomyDto:
      type: object
      properties:
        agentId:
          type: string
          example: dev-api
          description: Agent slug.
        actionType:
          type: string
          enum:
            - terminal
            - git_commit
            - git_push
            - pr_open
            - spend
            - git_repo_create
            - git_branch_create
            - git_branch_protect
            - write_file
            - open_adr_pr
            - git_merge
            - open_infra_pr
            - instruction_patch
            - parallelize
            - raise_max_parallel
            - propose_execution_plan
            - assess_implementability
            - container_start
            - container_stop
            - container_remove
            - container_recreate
            - container_start_via_runner
            - qa_environment_start
            - '*'
          example: terminal
          description: >-
            Action type, or `"*"` for EVERY action type of this agent — the
            "auto mode" of the ApprovalCard (RN-153). A SPECIFIC rule always
            wins over the wildcard: recording `terminal: deny` with `"*":
            auto_approve` already on still denies terminal.
        mode:
          type: string
          enum:
            - auto_approve
            - require_approval
            - deny
          example: auto_approve
          description: >-
            This agent's autonomy for this type. Does NOT override
            `permissions.json`: a pattern in `deny` stays blocked no matter how
            much autonomy the agent has.
      required:
        - agentId
        - actionType
        - mode
    SetAreaBudgetDto:
      type: object
      properties:
        limitUsd:
          type: object
          example: 20
          minimum: 0
          nullable: true
          description: >-
            Cap in DOLLARS for the area — same dollar-in, micro-USD-out
            convention as the project/session budgets (`BudgetsController`).
            `null` CLEARS the cap: the area goes back to unlimited, which is
            also the default for an area that never had one set.
      required:
        - limitUsd
    SetMaxParallelDto:
      type: object
      properties:
        maxParallel:
          type: number
          example: 4
          minimum: 1
          description: >-
            How many agents the area's lead can have in the session WITHOUT
            requesting your authorization. Minimum 1 — zero does not mean
            "unlimited", it means invalid configuration, and treating it as
            unlimited would turn a typo into unrestricted spending.
      required:
        - maxParallel
    SetMirrorPathDto:
      type: object
      properties:
        mirrorPath:
          type: string
          nullable: true
          example: /home/you/mirrors/store
          description: >-
            The absolute path, ON THE USER'S MACHINE, where the local agent
            copies the project work to (RN-515, ADR 0147). Send `null` — the key
            is REQUIRED, omitting it is a 400 — to clear the destination and
            turn the mirror off; `null` is the normal state of a project, not an
            error. Only `mounted`/`runner` projects can have one: in `container`
            the source is a server-side managed volume the local agent cannot
            see, and the request is refused with 400 naming that reason. The API
            validates ONLY the LEXICAL shape (absolute, no `..`/`.`, never the
            root, a system folder, or overlapping Brabo's own checkout) plus the
            two directions of the origin↔destination loop (the destination
            cannot be inside `workspacePath`, nor contain it) — it never touches
            the disk, because it cannot see the machine where the destination
            will live, exactly as in `runner` mode (RN-423). Resolving symlinks
            is the local agent’s half of the guard, not this one.
      required:
        - mirrorPath
    SetModelBindingDto:
      type: object
      properties:
        modelId:
          type: string
          format: uuid
          example: 9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b
          description: Model to pin to this scope. Must exist in `GET /models`.
        routingPreference:
          type: string
          enum:
            - price
            - throughput
            - latency
          nullable: true
          example: throughput
          description: >-
            How a HUB picks the upstream that serves the model (ADR 0166,
            RN-583). ABSENT keeps the stored value when the new model's provider
            accepts it, and clears it otherwise; `null` clears it; a value for a
            model whose provider does not declare the `routingPreference`
            capability (`GET /llm/provider-capabilities`) is refused with 422.
            It travels WITH this binding: it never cascades on its own.
      required:
        - modelId
    SetModelsActiveDto:
      type: object
      properties:
        modelIds:
          example:
            - 9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b
          description: Models to turn on or off. Whole batch or nothing.
          type: array
          items:
            type: string
            format: uuid
        isActive:
          type: boolean
          example: true
          description: >-
            `true` makes the model appear in the selector and be able to receive
            a new binding. Does not touch `availability`, which is what sync
            observed on the provider.
      required:
        - modelIds
        - isActive
    SetModelUsesDto:
      type: object
      properties:
        modelIds:
          example:
            - 9b1c2d3e-4f50-4a61-8b72-0c3d4e5f6a7b
          description: Models to mark. Whole batch or nothing.
          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: Automatically approved patterns.
          type: array
          items:
            type: string
        deny:
          example:
            - Terminal(rm -rf *)
            - Terminal(git push --force*)
          description: >-
            Forbidden patterns. `deny` beats `allow` and beats the agent's
            autonomy.
          type: array
          items:
            type: string
        ask:
          example:
            - Terminal(pnpm add *)
          description: Patterns that always ask for a human decision.
          type: array
          items:
            type: string
      required:
        - allow
        - deny
        - ask
    SetSessionResponseLanguageDto:
      type: object
      properties:
        language:
          type: string
          nullable: true
          example: en
          maxLength: 35
          description: >-
            The language agents answer YOU in, in THIS session only (RN-618) —
            other participants are never affected. Any BCP-47 code the server
            recognizes, stored canonical. Send `null` (or `automatico`) — the
            key is REQUIRED — to go back to inheriting from your Account.
      required:
        - language
    SetToolRouterDto:
      type: object
      properties:
        enabled:
          type: boolean
          example: false
          description: >-
            Whether the Jev chooses the tool of each agent step. On by default;
            it only acts when the turn model is from 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`: the model vanished from the provider catalog.
            `sem_tool_calling`: the request is from an agent and the model is
            chat-only — the cascade revalidates the capability at every level so
            as not to silently violate RN-040.
      required:
        - scope
        - modelId
        - reason
    SocketTicketResponseDto:
      type: object
      properties:
        ticket:
          type: string
          description: >-
            Opaque, single-use, base64url token. 30s TTL and single use: it
            disappears after the first successful `connect/3`, or when it
            expires.
        expiresAt:
          type: string
          example: '2026-08-09T12:00:30.000Z'
          description: When the ticket expires (ISO 8601).
      required:
        - ticket
        - expiresAt
    SpendLinhaResponseDto:
      type: object
      properties:
        chave:
          type: string
          example: anthropic/claude-sonnet-4
          description: >-
            The grouping key: model name, provider name, project id, actor id,
            or session id, depending on which list the row appears in.
        rotulo:
          type: object
          example: Loja
          nullable: true
          description: >-
            Readable name, when there is a table with a name (project). `null`
            when the key is already the label.
        actorKind:
          type: object
          example: agent
          nullable: true
          description: >-
            Only the per-actor lists fill this in (`porAtor`, `porOwner`,
            `porAgente`); on the others it is `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: 'Day in UTC. The series is DENSE: a day with no spend comes as 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: Functional requirements.
          type: array
          items:
            type: string
        rnf:
          example:
            - The response stays under 200 ms at p95
          description: Non-functional requirements.
          type: array
          items:
            type: string
        businessRuleIds:
          example:
            - RN-014
          description: >-
            Business rules covered. This is where rule→story coverage is
            computed in `GET /projects/:id/coverage`.
          type: array
          items:
            type: string
        dod:
          example:
            - Unit tests green
          description: Definition of done.
          type: array
          items:
            type: string
        dor:
          example:
            - Module defined
          description: Definition of ready.
          type: array
          items:
            type: string
        moduleIds:
          example:
            - api
          description: >-
            Modules from the `module_map` that the story touches. A story with
            no module, or with a nonexistent module, becomes an architecture
            pending item.
          type: array
          items:
            type: string
        status:
          type: string
          enum:
            - draft
            - ready
            - in_progress
            - done
          example: ready
        proposedReady:
          type: boolean
          example: true
          description: >-
            The PO finished the story and it's waiting on the user's decision
            (Phase 12c — RN-048). Coexists with `status: "draft"`: it is a
            proposal, not a state. Always `false` in a project in `auto` mode.
        returnedReason:
          type: object
          example: DoD too generic — spell out the acceptance criteria.
          nullable: true
          description: >-
            Why the user returned the story to the PO. `null` when it was never
            returned.
        returnedAt:
          type: object
          example: '2026-08-02T14:00:00.000Z'
          format: date-time
          nullable: true
        archivedAt:
          type: string
          example: null
          format: date-time
          description: >-
            When the story was ARCHIVED (RN-727). Archived stories leave the
            backlog, the coverage, the plan and the claim; `null` is a story in
            play.
          nullable: true
        archivedReason:
          type: string
          example: null
          description: Why it was archived, when someone said (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: Functional requirements.
          type: array
          items:
            type: string
        rnf:
          example:
            - The response stays under 200 ms at p95
          description: Non-functional requirements.
          type: array
          items:
            type: string
        businessRuleIds:
          example:
            - RN-014
          description: >-
            Business rules covered. This is where rule→story coverage is
            computed in `GET /projects/:id/coverage`.
          type: array
          items:
            type: string
        dod:
          example:
            - Unit tests green
          description: Definition of done.
          type: array
          items:
            type: string
        dor:
          example:
            - Module defined
          description: Definition of ready.
          type: array
          items:
            type: string
        moduleIds:
          example:
            - api
          description: >-
            Modules from the `module_map` that the story touches. A story with
            no module, or with a nonexistent module, becomes an architecture
            pending item.
          type: array
          items:
            type: string
        status:
          type: string
          enum:
            - draft
            - ready
            - in_progress
            - done
          example: ready
        proposedReady:
          type: boolean
          example: true
          description: >-
            The PO finished the story and it's waiting on the user's decision
            (Phase 12c — RN-048). Coexists with `status: "draft"`: it is a
            proposal, not a state. Always `false` in a project in `auto` mode.
        returnedReason:
          type: object
          example: DoD too generic — spell out the acceptance criteria.
          nullable: true
          description: >-
            Why the user returned the story to the PO. `null` when it was never
            returned.
        returnedAt:
          type: object
          example: '2026-08-02T14:00:00.000Z'
          format: date-time
          nullable: true
        archivedAt:
          type: string
          example: null
          format: date-time
          description: >-
            When the story was ARCHIVED (RN-727). Archived stories leave the
            backlog, the coverage, the plan and the claim; `null` is a story in
            play.
          nullable: true
        archivedReason:
          type: string
          example: null
          description: Why it was archived, when someone said (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: >-
            Asks for the WHOLE tool catalog: the Jev tool router (ADR 0179) is
            skipped for this call. The engine sets it when it repeats a step
            whose restricted menu made the model answer without calling a tool.
      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: The dev agent holding it.
      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 of the dev agent that claimed the task.
        blocked:
          type: boolean
          example: false
          description: Task blocked waiting on a decision or a dependency.
        blockedReason:
          type: object
          example: null
          nullable: true
        blockedOrigin:
          type: string
          enum:
            - infra
            - modelo
            - codigo
            - politica
          example: null
          nullable: true
          description: >-
            The ORIGIN of the block (ADR 0020/0038), when known. `null` for
            every Phase 4a block — only the QA Lead (Phase 8b) fills it in.
        gateStatus:
          type: string
          enum:
            - awaiting_qa
            - awaiting_secops
            - awaiting_user
          example: null
          nullable: true
          description: >-
            Where the task's PR is in the gate pipeline: QA, then SecOps, then
            the user. `null` while no PR is open.
        gateCorrectionCount:
          type: number
          example: 0
          description: How many correction rounds the gates have already requested.
        module:
          type: object
          example: api
          nullable: true
          description: >-
            The module the task belongs to, assigned by the Dev Lead in the
            approved execution plan (RN-678). Only the dev agent of that module
            claims it. `null` while no plan assigned one — such a task is
            claimable only when its story has exactly one module.
        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: Already deserialized; the engine dispatches to the tool by `name`.
      required:
        - id
        - name
        - arguments
    ToolRoutingResponseDto:
      type: object
      properties:
        modelo:
          type: string
          example: typesafe/jev-1.13
        ofertadas:
          type: number
          example: 9
          description: How many tools the agent had at this step.
        menuAntes:
          description: The tool names BEFORE the router.
          type: array
          items:
            type: string
        menuDepois:
          description: >-
            The tool names the chat model was offered (the whole catalog on any
            fall).
          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: The previous tool of the same run.
        aplicado:
          type: boolean
          description: The Jev answered AND the menu came out smaller than the catalog.
        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: REAL cost of the Jev answer (`usage.cost`), in micro-USD.
        gastoNaoRegistrado:
          type: boolean
          description: >-
            `true` when the Jev charged but the `token_usage` row could not be
            written.
      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 of the user who becomes the owner of record. Must already be an
            `owner` of this workspace.
      required:
        - userId
    TransitionSessionDto:
      type: object
      properties:
        status:
          type: string
          enum:
            - created
            - active
            - closing
            - closed
            - closed_abnormally
          example: closing
          description: >-
            Target state. The state machine refuses an invalid jump with 409 —
            from `created` you can only go to `active`, and there is no going
            back from a terminal state.
      required:
        - status
    UnblockTaskInternalDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        agentId:
          type: string
          example: dev-api
          description: The dev agent whose transient failure scheduled the retry.
      required:
        - projectId
        - agentId
    UnreadCursorDto:
      type: object
      properties:
        projectId:
          type: string
          format: uuid
          example: 01JC4Z0000PROJETO0000000001
        afterSeq:
          type: number
          example: 41
          minimum: 0
          description: >-
            Last `seq` THIS browser has already seen in this project. Returns
            events with strictly greater `seq`. `0` means "never saw anything"
            and brings the start of the most recent session.
      required:
        - projectId
        - afterSeq
    UnreadEventsDto:
      type: object
      properties:
        cursors:
          maxItems: 200
          description: >-
            One cursor per project to query. An EMPTY list returns an empty list
            — "I asked for nothing" is not "give me everything". A project from
            another workspace is silently ignored, because the cursor comes from
            the caller's local storage and may carry leftovers from an old
            workspace.
          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: Price per million INPUT tokens, in micro-USD.
        outputPricePerMillionMicros:
          type: number
          example: 10000000
          description: Price per million OUTPUT tokens, in 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. Unique within the workspace.
        maxConsecutiveBlocked:
          type: number
          example: 3
          description: >-
            Circuit breaker (Phase 12b — RN-047): how many consecutive tasks
            ending blocked stop the module's dev agent at idle_tripped. Omitted
            uses the domain default (DEFAULT_MAX_CONSECUTIVE_BLOCKED). Applies
            from the NEXT execution activation onward — doesn't affect agents
            already running.
        storyPromotion:
          type: string
          enum:
            - manual
            - auto
          example: auto
          description: >-
            Who promotes a story from `draft` to `ready` (Phase 12c — RN-048).
            `auto` (new-project default since RN-659): a complete story is
            already born `ready`, with no human step. `manual`: the PO proposes
            and YOU decide, in the Backlog tab. Changing the default did not
            rewrite any existing project. The domain validations
            (DoD/DoR/RF/rule/modules) are the SAME in both modes; the mode only
            changes who triggers it.
        language:
          type: string
          example: pt-BR
          maxLength: 35
          description: >-
            The PROJECT's language (RN-619) — shared artifacts and turns with no
            human author use it. Any BCP-47 code the server recognizes, stored
            canonical; an unrecognized one is a 400. Omitted, the project starts
            with its creator's effective response language (RN-618: account
            choice > confirmed detection > interface language).
    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): an open question the user ALREADY answered — only
            then a `ready` story (no task in execution) can be corrected.
      required:
        - projectId
    UpdateStoryTitleDto:
      type: object
      properties:
        title:
          type: string
          example: Register a user
          description: Trimmed; 1 to 200 characters.
      required:
        - title
    UpdateUserPreferencesDto:
      type: object
      properties:
        locale:
          type: string
          enum:
            - pt-BR
            - en
            - ru
            - zh-Hans
          example: en
          description: The interface language. Closed to the list — not free-form BCP-47.
        responseLanguage:
          type: string
          example: es
          maxLength: 35
          description: >-
            The language agents answer in (RN-618): `automatico` or ANY BCP-47
            code whose language the server recognizes — an open list, unlike
            `locale`. Stored canonical (`pt-br` becomes `pt-BR`). An
            unrecognized code is a 400 that names the rule. Never changes
            `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. Unique in the system and visible in the URL.
    UpsertBudgetDto:
      type: object
      properties:
        limitUsd:
          type: number
          example: 50
          minimum: 0
          description: >-
            Cap in DOLLARS — the only place in the contract that speaks in
            dollars, because that is what a person types. The api converts it to
            micro-USD, and responses come back in micro-USD.
        policy:
          type: string
          enum:
            - block
            - allow
          example: block
          description: >-
            `block` refuses the call when it would exceed the cap; `allow` lets
            it through and only warns.
      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: >-
            The key in plain text. It is encrypted via envelope encryption
            before touching the database and NEVER comes back in any response —
            not even encrypted. The 512-character cap is protection against an
            absurd payload, not format validation: whether the key is any good
            is answered by the provider, at `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: Covers LLM keys and git tokens (`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` (every account starts there) or the BCP-47 code
            explicitly chosen on the Account page.
        detectedLanguage:
          type: string
          nullable: true
          example: null
          description: >-
            The language detected from your own messages AND confirmed by you —
            never an unconfirmed detection. `null` until a confirmation exists.
        detectedLanguageConfirmedAt:
          type: string
          format: date-time
          nullable: true
        effectiveResponseLanguage:
          description: >-
            What applies today OUTSIDE any session, and where it came from:
            account choice > confirmed detection > interface language.
          allOf:
            - $ref: '#/components/schemas/IdiomaEfetivoResponseDto'
      required:
        - locale
        - responseLanguage
        - detectedLanguage
        - detectedLanguageConfirmedAt
        - effectiveResponseLanguage
    VerifyEmailDto:
      type: object
      properties:
        token:
          type: string
          description: Single-use token received by email.
      required:
        - token
    WorkspaceComPapelResponseDto:
      type: object
      properties:
        workspace:
          $ref: '#/components/schemas/WorkspaceResponseDto'
        role:
          type: string
          enum:
            - viewer
            - developer
            - maintainer
            - owner
          example: maintainer
          description: The CALLER's role in this workspace.
      required:
        - workspace
        - role
    WorkspaceMemberComUsuarioResponseDto:
      type: object
      properties:
        userId:
          type: string
          example: 01JC4Z0000USUARIO0000000002
        role:
          type: string
          enum:
            - viewer
            - developer
            - maintainer
            - owner
          example: maintainer
          description: >-
            The WORKSPACE role — what every project of the workspace inherits
            unless a project row overrides it (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: >-
            Linear hierarchy: owner > maintainer > developer > viewer. Each role
            includes the permissions of the ones after it.
        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: Unique in the system; it's what appears in the URL.
        createdBy:
          type: string
          example: 01JC4Z0000USUARIO0000000001
        toolRouterEnabled:
          type: boolean
          example: true
          description: >-
            Whether the tool-routing step by the Jev (ADR 0179) runs for this
            workspace. On by default; it only acts when the turn model is from
            OpenRouter, so `true` on a workspace without OpenRouter does
            nothing.
        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: Owner of the keys that funded this spend (RN-058).
        dias:
          type: number
          example: 30
          description: Sliding window, in days.
        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: >-
            By MODEL. Two providers serving the same name fall into the same row
            — separating them would reintroduce the credential axis under
            another name.
          type: array
          items:
            $ref: '#/components/schemas/SpendLinhaResponseDto'
        porProvider:
          description: >-
            By PROVIDER (ADR 0076, RN-186). This axis speaks of CREDENTIAL and
            so only exists here, on a route that requires `owner` (RN-060) — the
            member's response does not have it, and it is not a filter that
            hides it: it is the repository's type that refuses it with an actor
            scope (RN-187).
          type: array
          items:
            $ref: '#/components/schemas/SpendLinhaResponseDto'
        porProjeto:
          description: By PROJECT within the workspace.
          type: array
          items:
            $ref: '#/components/schemas/SpendLinhaResponseDto'
        porSessao:
          description: By SESSION (RN-952).
          type: array
          items:
            $ref: '#/components/schemas/SpendLinhaResponseDto'
        porAtor:
          description: >-
            By ACTOR — agent and person in the same list, distinguished by
            `actorKind`.
          type: array
          items:
            $ref: '#/components/schemas/SpendLinhaResponseDto'
        porOwner:
          description: >-
            The PERSON rows (`actor_kind = "user"`) — the block the handoff
            calls "By owner", because under RN-058 it is the owner's key that
            all of them spend. Who the owner is sits in `ownerId`. A partition
            of `porAtor`, with no extra query (RN-188).
          type: array
          items:
            $ref: '#/components/schemas/SpendLinhaResponseDto'
        porAgente:
          description: >-
            The AGENT rows (`actor_kind = "agent"`). The other half of
            `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: >-
            Number of projects in the workspace. There is no "active" flag in
            the domain — every project counts.
        agentCount:
          type: number
          example: 6
          description: >-
            Distinct agents (actorKind=agent) that spent tokens this month,
            summed across all of the workspace's projects — includes area
            subspecialties (Phase 8).
        spentMicros:
          type: number
          example: 12500000
          description: >-
            Current month's spend, in micro-USD, summed by
            token_usage.createdAt.
      required:
        - activeProjects
        - agentCount
        - spentMicros
