openapi: 3.1.0
info:
  title: KnownScope API
  version: 0.5.0
  summary: Public API for coordinated emerging-threat response work.
  description: |
    The first-party web application and external integrations share this API.
    Stage 3 adds revisioned Campaign and Item work updates, inline mentions,
    and one permission-filtered activity stream while retaining unauthenticated
    health and non-sensitive build metadata.
    Browser sessions use a secure host-only cookie. Unsafe browser requests also
    send the exact application Origin and the X-CSRF-Token returned by the
    authenticated Session resource.
  license:
    name: License not yet selected
servers:
  - url: /
    description: Same-origin deployment
security:
  - browserSession: []
tags:
  - name: Operations
    description: Liveness and readiness endpoints for the deployment platform.
  - name: System
    description: Non-sensitive application metadata used by first-party clients.
  - name: Session
    description: The authenticated tenant and user context.
  - name: Authentication
    description: Browser sign-in, recovery authentication, and session lifecycle.
  - name: Directory
    description: Tenant users, groups, templates, and additive global grants.
  - name: Tag Catalog
    description: Tenant-governed namespaces and stable controlled-tag identities.
  - name: Campaigns
    description: Response efforts, membership, ownership, and lifecycle.
  - name: Campaign Items
    description: Validated row-like objects tracked within a Campaign.
  - name: Campaign Fields
    description: Versioned Field Definitions and reviewed schema proposals.
  - name: Campaign Imports
    description: Governed inspection, mapping, dry-run, and asynchronous tabular ingestion.
  - name: Campaign Exports
    description: Permission-checked, policy-aware Campaign Item downloads.
  - name: Collaboration
    description: Campaign and Item work logs, inline mentions, revisions, and combined activity.
  - name: Audit
    description: Permission-filtered, append-only Campaign activity.
paths:
  /healthz:
    get:
      operationId: getLiveness
      summary: Check process liveness
      description: Reports whether the process can serve HTTP. It does not check dependencies.
      tags: [Operations]
      security: []
      responses:
        "200":
          description: The API process is alive.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Liveness"
  /readyz:
    get:
      operationId: getReadiness
      summary: Check traffic readiness
      description: Reports whether required dependencies are available within a bounded timeout.
      tags: [Operations]
      security: []
      responses:
        "200":
          description: The API is ready to receive traffic.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Readiness"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /metrics:
    get:
      operationId: getMetrics
      summary: Read process metrics
      description: |
        Exposes bounded Prometheus text metrics. Production ingress must restrict
        this endpoint to the monitoring network.
      tags: [Operations]
      security: []
      responses:
        "200":
          description: Current process metrics in the Prometheus text exposition format.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            text/plain:
              schema:
                type: string
  /api/v1/system/info:
    get:
      operationId: getSystemInfo
      summary: Read public application metadata
      description: Returns non-sensitive version metadata for compatibility and diagnostics.
      tags: [System]
      security: []
      responses:
        "200":
          description: Application metadata.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SystemInfo"
  /api/v1/auth/methods:
    get:
      operationId: getAuthenticationMethods
      summary: Discover configured browser sign-in methods
      tags: [Authentication]
      security: []
      responses:
        "200":
          description: Non-sensitive authentication method metadata.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthenticationMethods"
  /api/v1/auth/login:
    get:
      operationId: beginOIDCLogin
      summary: Begin the configured OIDC authorization-code flow
      tags: [Authentication]
      security: []
      parameters:
        - name: returnTo
          in: query
          required: false
          schema:
            type: string
            minLength: 1
            maxLength: 1024
            default: /
      responses:
        "200":
          description: OIDC authorization destination and browser binding.
          headers:
            Set-Cookie:
              $ref: "#/components/headers/SetCookie"
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OIDCAuthorization"
        "400":
          $ref: "#/components/responses/BadRequest"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /api/v1/auth/callback:
    get:
      operationId: completeOIDCLogin
      summary: Complete the configured OIDC authorization-code flow
      tags: [Authentication]
      security: []
      parameters:
        - name: state
          in: query
          required: true
          schema:
            type: string
            minLength: 32
            maxLength: 256
        - name: code
          in: query
          required: true
          schema:
            type: string
            minLength: 1
            maxLength: 4096
        - name: __Host-knownscope_session_login
          in: cookie
          required: true
          schema:
            type: string
            minLength: 32
            maxLength: 256
      responses:
        "200":
          description: >-
            Session completion result for a non-navigating client. Browser
            authorization flows receive the redirect response below.
          headers:
            Set-Cookie:
              $ref: "#/components/headers/SetCookie"
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthenticationRedirect"
        "303":
          description: Establish a KnownScope session and return to the application.
          headers:
            Location:
              description: Validated same-origin application path.
              required: true
              schema:
                type: string
                minLength: 1
                maxLength: 1024
            Set-Cookie:
              $ref: "#/components/headers/SetCookie"
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
        "400":
          $ref: "#/components/responses/BadRequest"
        "403":
          $ref: "#/components/responses/Forbidden"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /api/v1/auth/break-glass:
    post:
      operationId: createBreakGlassSession
      summary: Authenticate the installation recovery administrator
      description: >-
        Emergency recovery path for the single local administrator. Invalid
        credentials return a generic response and persistent lockout is enforced.
      tags: [Authentication]
      security: []
      parameters:
        - name: Origin
          in: header
          required: true
          schema:
            type: string
            format: uri
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BreakGlassLoginRequest"
      responses:
        "200":
          description: Recovery session established.
          headers:
            Set-Cookie:
              $ref: "#/components/headers/SetCookie"
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthenticationRedirect"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/TooManyRequests"
  /api/v1/auth/logout:
    post:
      operationId: logout
      summary: Revoke the current browser session
      tags: [Authentication]
      responses:
        "204":
          description: Session revoked or the development adapter remained active.
          headers:
            Set-Cookie:
              $ref: "#/components/headers/SetCookie"
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /api/v1/session:
    get:
      operationId: getSession
      summary: Read the current authenticated session
      tags: [Session]
      responses:
        "200":
          description: Current tenant and user context.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Session"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /api/v1/users:
    get:
      operationId: listUsers
      summary: List active users in the current tenant directory
      tags: [Directory]
      responses:
        "200":
          description: Active tenant users.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required: [items]
                properties:
                  items:
                    type: array
                    maxItems: 200
                    items:
                      $ref: "#/components/schemas/User"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /api/v1/groups:
    get:
      operationId: listGroups
      summary: List active groups in the current tenant
      tags: [Directory]
      responses:
        "200":
          description: Active tenant groups.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required: [items]
                properties:
                  items:
                    type: array
                    maxItems: 200
                    items:
                      $ref: "#/components/schemas/Group"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
    post:
      operationId: createGroup
      summary: Create a tenant group
      tags: [Directory]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateGroupRequest"
      responses:
        "201":
          description: Group created.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Group"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
  /api/v1/groups/{groupId}/members:
    parameters:
      - $ref: "#/components/parameters/GroupID"
    get:
      operationId: listGroupMembers
      summary: List the active members of a tenant group
      tags: [Directory]
      responses:
        "200":
          description: Active users currently assigned to the group.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required: [items]
                properties:
                  items:
                    type: array
                    maxItems: 200
                    items:
                      $ref: "#/components/schemas/User"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
  /api/v1/groups/{groupId}/members/{userId}:
    parameters:
      - $ref: "#/components/parameters/GroupID"
      - $ref: "#/components/parameters/UserID"
    put:
      operationId: addGroupMember
      summary: Add a user to a tenant group
      tags: [Directory]
      responses:
        "204":
          description: Group member added.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
    delete:
      operationId: removeGroupMember
      summary: Remove a user from a tenant group
      tags: [Directory]
      responses:
        "204":
          description: Group member removed.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
  /api/v1/campaign-templates:
    get:
      operationId: listCampaignTemplates
      summary: List active Campaign templates
      tags: [Directory]
      responses:
        "200":
          description: Active, immutable template versions.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required: [items]
                properties:
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/CampaignTemplate"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
    post:
      operationId: createCampaignTemplate
      summary: Create and publish the first immutable Campaign template version
      tags: [Directory]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateCampaignTemplateRequest"
      responses:
        "201":
          description: Campaign template version 1 published.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CampaignTemplate"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
  /api/v1/campaign-templates/{templateId}/versions:
    parameters:
      - $ref: "#/components/parameters/TemplateID"
    get:
      operationId: listCampaignTemplateVersions
      summary: List immutable versions of a Campaign template
      tags: [Directory]
      responses:
        "200":
          description: Published versions, newest first.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required: [items]
                properties:
                  items:
                    type: array
                    maxItems: 200
                    items:
                      $ref: "#/components/schemas/CampaignTemplateVersion"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
    post:
      operationId: publishCampaignTemplateVersion
      summary: Publish the next immutable Campaign template version
      tags: [Directory]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PublishCampaignTemplateVersionRequest"
      responses:
        "201":
          description: Next Campaign template version published.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CampaignTemplateVersion"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /api/v1/tag-namespaces:
    get:
      operationId: listTagCatalog
      summary: List tenant tag namespaces and active or retired tags
      description: >-
        Active tenant members may resolve catalog labels. Retired tags remain
        visible so historical Campaign values do not become opaque.
      tags: [Tag Catalog]
      responses:
        "200":
          description: Complete bounded tenant tag catalog.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TagCatalog"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
    post:
      operationId: createTagNamespace
      summary: Create a governed tag namespace
      tags: [Tag Catalog]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateTagNamespaceRequest"
      responses:
        "201":
          description: Namespace created.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TagNamespace"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
  /api/v1/tag-namespaces/{namespaceId}:
    parameters:
      - $ref: "#/components/parameters/TagNamespaceID"
    patch:
      operationId: updateTagNamespace
      summary: Edit a tag namespace label or help text
      description: Stable keys and namespace lifecycle are not mutable in Stage 2.
      tags: [Tag Catalog]
      parameters:
        - $ref: "#/components/parameters/IfMatch"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateTagNamespaceRequest"
      responses:
        "200":
          description: Namespace metadata updated.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TagNamespace"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /api/v1/tag-namespaces/{namespaceId}/tags:
    parameters:
      - $ref: "#/components/parameters/TagNamespaceID"
    post:
      operationId: createControlledTag
      summary: Create a stable tag in a namespace
      tags: [Tag Catalog]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateControlledTagRequest"
      responses:
        "201":
          description: Controlled tag created.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ControlledTag"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /api/v1/controlled-tags/{tagId}:
    parameters:
      - $ref: "#/components/parameters/ControlledTagID"
    patch:
      operationId: updateControlledTag
      summary: Edit or retire a stable controlled tag
      tags: [Tag Catalog]
      parameters:
        - $ref: "#/components/parameters/IfMatch"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateControlledTagRequest"
      responses:
        "200":
          description: Controlled tag updated.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ControlledTag"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /api/v1/global-role-assignments:
    get:
      operationId: listGlobalRoleAssignments
      summary: List additive tenant-wide Campaign grants
      tags: [Directory]
      responses:
        "200":
          description: Global Worker and Viewer grants.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required: [items]
                properties:
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/GlobalRoleAssignment"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
    post:
      operationId: upsertGlobalRoleAssignment
      summary: Assign or change an additive tenant-wide Campaign role
      tags: [Directory]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpsertRoleRequest"
      responses:
        "200":
          description: Global role assignment saved.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GlobalRoleAssignment"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
  /api/v1/global-role-assignments/{assignmentId}:
    parameters:
      - $ref: "#/components/parameters/AssignmentID"
    delete:
      operationId: removeGlobalRoleAssignment
      summary: Remove an additive tenant-wide Campaign role
      tags: [Directory]
      responses:
        "204":
          description: Global role assignment removed.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
  /api/v1/campaigns:
    get:
      operationId: listCampaigns
      summary: List Campaigns visible to the current user
      tags: [Campaigns]
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - name: sortBy
          in: query
          required: false
          schema:
            $ref: "#/components/schemas/CampaignSortField"
        - $ref: "#/components/parameters/SortDirection"
      responses:
        "200":
          description: Permission-filtered Campaign page.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CampaignPage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
    post:
      operationId: createCampaign
      summary: Create a Draft Campaign from a template snapshot
      tags: [Campaigns]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateCampaignRequest"
      responses:
        "201":
          description: Campaign created.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Campaign"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /api/v1/campaigns/{campaignId}:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
    get:
      operationId: getCampaign
      summary: Read one permitted Campaign
      tags: [Campaigns]
      responses:
        "200":
          description: Campaign detail.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Campaign"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
    patch:
      operationId: updateCampaign
      summary: Update Campaign metadata or lifecycle
      tags: [Campaigns]
      parameters:
        - $ref: "#/components/parameters/IfMatch"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateCampaignRequest"
      responses:
        "200":
          description: Campaign updated.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Campaign"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /api/v1/campaigns/{campaignId}/members:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
    get:
      operationId: listCampaignMembers
      summary: List the Owner and direct user/group Campaign memberships
      tags: [Campaigns]
      responses:
        "200":
          description: Campaign membership list.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required: [items]
                properties:
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/CampaignMember"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
    post:
      operationId: upsertCampaignMember
      summary: Add a user/group or change its direct Campaign role
      tags: [Campaigns]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpsertRoleRequest"
      responses:
        "200":
          description: Campaign membership saved.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CampaignMember"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
  /api/v1/campaigns/{campaignId}/members/{membershipId}:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
      - $ref: "#/components/parameters/MembershipID"
    delete:
      operationId: removeCampaignMember
      summary: Remove a direct user/group Campaign membership
      tags: [Campaigns]
      responses:
        "204":
          description: Campaign membership removed.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
  /api/v1/campaigns/{campaignId}/owner-transfer:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
    post:
      operationId: transferCampaignOwnership
      summary: Transfer primary ownership and retain the former Owner as a Worker
      tags: [Campaigns]
      parameters:
        - $ref: "#/components/parameters/IfMatch"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [newOwnerUserId]
              properties:
                newOwnerUserId:
                  type: string
                  format: uuid
      responses:
        "200":
          description: Ownership transferred.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Campaign"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /api/v1/campaigns/{campaignId}/fields:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
    get:
      operationId: listCampaignFields
      summary: List Field Definitions for a Campaign schema version
      tags: [Campaign Fields]
      parameters:
        - name: schemaVersion
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
      responses:
        "200":
          description: Ordered Field Definitions for the requested or current schema.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FieldDefinitionPage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
    post:
      operationId: createCampaignField
      summary: Add an optional Field Definition to a Campaign
      tags: [Campaign Fields]
      parameters:
        - $ref: "#/components/parameters/IfMatch"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateFieldDefinition"
      responses:
        "201":
          description: Field Definition created in a new Campaign schema version.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FieldDefinitionMutation"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /api/v1/campaigns/{campaignId}/schema-proposals:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
    get:
      operationId: listCampaignSchemaProposals
      summary: List schema proposals and their decisions
      tags: [Campaign Fields]
      responses:
        "200":
          description: Pending and decided schema proposals visible in this Campaign.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SchemaProposalPage"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
    post:
      operationId: submitCampaignSchemaProposal
      summary: Propose an optional Field Definition
      description: Workers and Owners may submit an additive field for Owner review.
      tags: [Campaign Fields]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SubmitSchemaProposalRequest"
      responses:
        "201":
          description: Immutable schema proposal submitted for review.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SchemaProposalMutation"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /api/v1/campaigns/{campaignId}/schema-proposals/{proposalId}/decision:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
      - $ref: "#/components/parameters/ProposalID"
    post:
      operationId: decideCampaignSchemaProposal
      summary: Approve or reject a schema proposal
      description: Approval publishes exactly one new Campaign schema version.
      tags: [Campaign Fields]
      parameters:
        - $ref: "#/components/parameters/IfMatch"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DecideSchemaProposalRequest"
      responses:
        "200":
          description: Owner decision recorded.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SchemaProposalMutation"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /api/v1/campaigns/{campaignId}/schema-draft:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
    get:
      operationId: getActiveCampaignSchemaDraft
      summary: Get the caller's active Campaign schema draft
      tags: [Campaign Fields]
      responses:
        "200":
          description: Zero or one active schema draft owned by the caller.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SchemaDraftPage"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
    put:
      operationId: saveCampaignSchemaDraft
      summary: Create or update the caller's active Campaign schema draft
      description: Workers may place additions without reordering existing fields. Owners may also reorder existing fields.
      tags: [Campaign Fields]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SaveSchemaDraftRequest"
      responses:
        "200":
          description: Schema draft saved without changing the active Campaign schema.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SchemaDraftMutation"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /api/v1/campaigns/{campaignId}/schema-drafts/{draftId}/submit:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
      - $ref: "#/components/parameters/DraftID"
    post:
      operationId: submitCampaignSchemaDraft
      summary: Submit a persisted schema draft for Owner review
      tags: [Campaign Fields]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FinalizeSchemaDraftRequest"
      responses:
        "201":
          description: The complete draft was captured as one immutable schema proposal.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SchemaProposalMutation"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /api/v1/campaigns/{campaignId}/schema-drafts/{draftId}/publish:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
      - $ref: "#/components/parameters/DraftID"
    post:
      operationId: publishCampaignSchemaDraft
      summary: Publish an Owner's complete schema draft atomically
      description: Additions and canonical order become exactly one immutable Campaign schema version.
      tags: [Campaign Fields]
      parameters:
        - $ref: "#/components/parameters/IfMatch"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FinalizeSchemaDraftRequest"
      responses:
        "200":
          description: Complete schema change set published.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SchemaPublicationMutation"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /api/v1/campaigns/{campaignId}/items:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
    get:
      operationId: listCampaignItems
      summary: List active Campaign Items
      tags: [Campaign Items]
      parameters:
        - $ref: "#/components/parameters/ItemLimit"
        - $ref: "#/components/parameters/Cursor"
        - name: sortBy
          in: query
          required: false
          schema:
            type: string
            minLength: 2
            maxLength: 64
            default: updated_at
          description: Stable Field Definition key, or updated_at.
        - $ref: "#/components/parameters/SortDirection"
        - name: filterField
          in: query
          required: false
          schema:
            type: string
            minLength: 2
            maxLength: 64
          description: One active filterable stable Field Definition key.
        - name: filterOperator
          in: query
          required: false
          schema:
            $ref: "#/components/schemas/ItemFilterOperator"
        - name: filterValue
          in: query
          required: false
          schema:
            type: string
            maxLength: 10000
      responses:
        "200":
          description: Paginated Campaign Item list.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CampaignItemPage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
    post:
      operationId: createCampaignItem
      summary: Add a Campaign Item
      tags: [Campaign Items]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateCampaignItemRequest"
      responses:
        "201":
          description: Campaign Item created.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CampaignItem"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
  /api/v1/campaigns/{campaignId}/items/bulk-update:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
    post:
      operationId: bulkUpdateCampaignItems
      summary: Apply one retry-safe update to a bounded Item selection
      tags: [Campaign Items]
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BulkUpdateCampaignItemsRequest"
      responses:
        "200":
          description: Per-Item bulk update outcomes, including partial conflicts.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
            Idempotency-Replayed:
              description: True when the stored response for this key was replayed.
              schema:
                type: boolean
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BulkUpdateCampaignItemsResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /api/v1/campaigns/{campaignId}/items/bulk-archive:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
    post:
      operationId: bulkArchiveCampaignItems
      summary: Remove a bounded Item selection from the active working set
      description: >-
        Owners and Workers may soft-archive up to 100 selected Items. Each Item
        is checked against the selected version; conflicts and unavailable rows
        are reported without hiding successful removals.
      tags: [Campaign Items]
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BulkArchiveCampaignItemsRequest"
      responses:
        "200":
          description: Per-Item removal outcomes, including partial conflicts.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
            Idempotency-Replayed:
              description: True when the stored response for this key was replayed.
              schema:
                type: boolean
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BulkArchiveCampaignItemsResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /api/v1/campaigns/{campaignId}/exports:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
    post:
      operationId: exportCampaignItems
      summary: Download a bounded, permission-aware Campaign Item export
      description: >-
        Exports active Items using the current Campaign schema. Owners and
        Workers may export. Excluded fields are unavailable, Restricted values
        are always redacted, and successful generation is audited.
      tags: [Campaign Exports]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CampaignItemExportRequest"
      responses:
        "200":
          description: Formula-safe CSV bytes or a structured JSON export.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
            X-Export-Item-Count:
              description: Number of active Campaign Items in this export.
              schema:
                type: integer
                minimum: 0
                maximum: 10000
            Content-Disposition:
              description: Safe attachment filename.
              schema:
                type: string
            Cache-Control:
              description: Prevents storage of sensitive generated content.
              schema:
                type: string
          content:
            text/csv:
              schema:
                type: string
            application/json:
              schema:
                $ref: "#/components/schemas/CampaignItemExportDocument"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
  /api/v1/campaigns/{campaignId}/item-views:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
    get:
      operationId: listCampaignItemViews
      summary: List visible personal and team Item-grid views
      tags: [Campaign Items]
      responses:
        "200":
          description: Team views plus the current user's personal views.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GridViewPage"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
    post:
      operationId: createCampaignItemView
      summary: Save a personal or Owner-managed team Item-grid view
      tags: [Campaign Items]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateGridViewRequest"
      responses:
        "201":
          description: Grid view saved.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GridViewMutation"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /api/v1/campaigns/{campaignId}/item-views/{viewId}:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
      - $ref: "#/components/parameters/GridViewID"
      - $ref: "#/components/parameters/IfMatch"
    put:
      operationId: updateCampaignItemView
      summary: Replace a visible saved Item-grid view
      tags: [Campaign Items]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateGridViewRequest"
      responses:
        "200":
          description: Grid view updated.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GridViewMutation"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
    delete:
      operationId: deleteCampaignItemView
      summary: Delete a personal or Owner-managed team Item-grid view
      tags: [Campaign Items]
      responses:
        "204":
          description: Grid view deleted.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /api/v1/campaigns/{campaignId}/items/{itemId}:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
      - $ref: "#/components/parameters/ItemID"
    get:
      operationId: getCampaignItem
      summary: Read one Campaign Item
      tags: [Campaign Items]
      responses:
        "200":
          description: Campaign Item detail.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CampaignItem"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
    put:
      operationId: updateCampaignItem
      summary: Replace the dynamic values of a Campaign Item
      tags: [Campaign Items]
      parameters:
        - $ref: "#/components/parameters/IfMatch"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CampaignItemWrite"
      responses:
        "200":
          description: Campaign Item updated.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CampaignItem"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
    delete:
      operationId: archiveCampaignItem
      summary: Remove a Campaign Item from the active working set
      description: >-
        Owners and Workers may archive an active Item. The Item disappears from
        active grids and exports, but its values, Work log, provenance, and
        audit history remain stored. This operation never hard-deletes the row.
      tags: [Campaign Items]
      parameters:
        - $ref: "#/components/parameters/IfMatch"
      responses:
        "204":
          description: Campaign Item archived.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /api/v1/campaigns/{campaignId}/activity:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
    get:
      operationId: listCampaignActivity
      summary: List material Campaign changes and work updates in one chronological stream
      tags: [Collaboration]
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
      responses:
        "200":
          description: Newest-first material activity without duplicate work-update audit cards.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ActivityPage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
  /api/v1/campaigns/{campaignId}/work-log/participants:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
    get:
      operationId: listCampaignWorkUpdateParticipants
      summary: List active people who may be mentioned in this Campaign
      tags: [Collaboration]
      responses:
        "200":
          description: Active users who already hold effective Campaign access.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkUpdateParticipantPage"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
  /api/v1/campaigns/{campaignId}/work-updates:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
    get:
      operationId: listCampaignWorkUpdates
      summary: List Campaign work updates, optionally narrowed to one Item
      tags: [Collaboration]
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - name: itemId
          in: query
          required: false
          description: Return only work updates scoped to this Campaign Item.
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Newest-first work-update page, including retained tombstones.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkUpdatePage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
    post:
      operationId: createCampaignWorkUpdate
      summary: Add a Campaign- or Item-scoped work update
      tags: [Collaboration]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateWorkUpdateRequest"
      responses:
        "201":
          description: Work update and its first immutable revision created.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkUpdateMutation"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
  /api/v1/campaigns/{campaignId}/work-updates/bulk:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
    post:
      operationId: bulkCreateCampaignWorkUpdates
      summary: Append one retry-safe work update to each selected Item
      description: >-
        Owners and Workers may append the same work update to up to 100 active
        Items. Each Item is checked against the selected version; conflicts and
        unavailable rows are reported without rewriting prior updates.
      tags: [Collaboration]
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BulkCreateWorkUpdatesRequest"
      responses:
        "200":
          description: Per-Item append outcomes, including partial conflicts.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
            Idempotency-Replayed:
              description: True when the stored response for this key was replayed.
              schema:
                type: boolean
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BulkCreateWorkUpdatesResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /api/v1/campaigns/{campaignId}/work-updates/{workUpdateId}:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
      - $ref: "#/components/parameters/WorkUpdateID"
    patch:
      operationId: updateCampaignWorkUpdate
      summary: Append a corrected revision to the work update authored by the caller
      tags: [Collaboration]
      parameters:
        - $ref: "#/components/parameters/IfMatch"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateWorkUpdateRequest"
      responses:
        "200":
          description: New immutable revision appended and made current.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkUpdateMutation"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
    delete:
      operationId: deleteCampaignWorkUpdate
      summary: Replace a work update with a retained soft-delete tombstone
      tags: [Collaboration]
      parameters:
        - $ref: "#/components/parameters/IfMatch"
      responses:
        "200":
          description: Work-update tombstone created; revisions remain retained.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkUpdateMutation"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /api/v1/campaigns/{campaignId}/work-updates/{workUpdateId}/revisions:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
      - $ref: "#/components/parameters/WorkUpdateID"
    get:
      operationId: listCampaignWorkUpdateRevisions
      summary: List immutable revisions for one permitted work update
      tags: [Collaboration]
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
      responses:
        "200":
          description: Newest-first immutable revision page.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkUpdateRevisionPage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
  /api/v1/campaigns/{campaignId}/imports:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
    get:
      operationId: listCampaignImports
      summary: List recent governed imports for a Campaign
      tags: [Campaign Imports]
      responses:
        "200":
          description: Recent Campaign imports, newest first.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CampaignImportPage"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
    post:
      operationId: createCampaignImport
      summary: Upload and safely inspect one CSV, XLSX, or clipboard source
      description: The binary source is private, capped at 10 MiB, and retained for at most 24 hours before commit or cancellation.
      tags: [Campaign Imports]
      parameters:
        - name: sourceKind
          in: query
          required: true
          schema:
            $ref: "#/components/schemas/ImportSourceKind"
        - name: fileName
          in: query
          required: true
          schema:
            type: string
            minLength: 1
            maxLength: 255
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        "201":
          description: Source inspected and staged privately.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CampaignImport"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"
  /api/v1/campaigns/{campaignId}/imports/{importId}:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
      - $ref: "#/components/parameters/ImportID"
    get:
      operationId: getCampaignImport
      summary: Read import state, reviewed summary, and bounded outcomes
      tags: [Campaign Imports]
      responses:
        "200":
          description: Current import state; raw source bytes are never returned.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CampaignImport"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
    delete:
      operationId: cancelCampaignImport
      summary: Cancel an uncommitted import and erase its staged source
      tags: [Campaign Imports]
      parameters:
        - $ref: "#/components/parameters/IfMatch"
      responses:
        "200":
          description: Import cancelled and staged source erased.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CampaignImport"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /api/v1/campaigns/{campaignId}/imports/{importId}/preview:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
      - $ref: "#/components/parameters/ImportID"
    post:
      operationId: previewCampaignImport
      summary: Select worksheet, header, encoding, delimiter, and data rows
      tags: [Campaign Imports]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ImportShape"
      responses:
        "200":
          description: Bounded source preview with neutral columns and mapping suggestions.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CampaignImport"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /api/v1/campaigns/{campaignId}/imports/{importId}/dry-run:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
      - $ref: "#/components/parameters/ImportID"
    post:
      operationId: dryRunCampaignImport
      summary: Validate every selected row and classify intended writes
      tags: [Campaign Imports]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ImportConfiguration"
      responses:
        "200":
          description: Complete totals and bounded row-level preview, with no Campaign Item writes.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CampaignImport"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /api/v1/campaigns/{campaignId}/imports/{importId}/commit:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
      - $ref: "#/components/parameters/ImportID"
    post:
      operationId: commitCampaignImport
      summary: Queue the exact reviewed import plan for idempotent commit
      tags: [Campaign Imports]
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CommitImportRequest"
      responses:
        "202":
          description: Import queued; poll the import resource for progress and outcomes.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            X-Audit-Event-ID:
              $ref: "#/components/headers/AuditEventID"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CampaignImport"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /api/v1/campaigns/{campaignId}/imports/{importId}/errors.csv:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
      - $ref: "#/components/parameters/ImportID"
    get:
      operationId: downloadCampaignImportErrors
      summary: Download a permission-checked, formula-safe row error report
      tags: [Campaign Imports]
      responses:
        "200":
          description: CSV report containing correction guidance without raw source values.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
            Content-Disposition:
              description: Safe attachment filename.
              schema:
                type: string
          content:
            text/csv:
              schema:
                type: string
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
  /api/v1/campaigns/{campaignId}/audit-events:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
    get:
      operationId: listCampaignAuditEvents
      summary: List the permitted Campaign audit timeline
      tags: [Audit]
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - name: view
          in: query
          required: false
          description: >-
            Purpose-built audit view. Activity is the default material Campaign
            history. Operations requires Worker or Owner access; access and all
            require Owner access.
          schema:
            $ref: "#/components/schemas/AuditEventView"
      responses:
        "200":
          description: Append-only Campaign audit page.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuditEventPage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
  /api/v1/campaigns/{campaignId}/audit-events/{auditEventId}:
    parameters:
      - $ref: "#/components/parameters/CampaignID"
      - $ref: "#/components/parameters/AuditEventPathID"
    get:
      operationId: getCampaignAuditEvent
      summary: Inspect the permission-filtered values captured by one Campaign audit event
      tags: [Audit]
      responses:
        "200":
          description: >-
            Immutable event metadata, safe operational facts, and bounded typed
            before/after changes when value-level evidence was captured.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuditEventDetail"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
components:
  securitySchemes:
    browserSession:
      type: apiKey
      in: cookie
      name: __Host-knownscope_session
      description: Opaque secure browser session. Development uses the fixed local identity adapter.
  parameters:
    Limit:
      name: limit
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 25
    ItemLimit:
      name: limit
      in: query
      required: false
      description: Number of Campaign Items returned in one grid page.
      schema:
        type: integer
        minimum: 1
        maximum: 500
        default: 25
    Cursor:
      name: cursor
      in: query
      required: false
      schema:
        type: string
        minLength: 1
        maxLength: 512
    SortDirection:
      name: sortDirection
      in: query
      required: false
      schema:
        $ref: "#/components/schemas/SortDirection"
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: Actor-scoped retry key. Reusing it with a different body returns a conflict.
      schema:
        type: string
        minLength: 16
        maxLength: 64
        pattern: "^[A-Za-z0-9_-]+$"
    IfMatch:
      name: if-match
      in: header
      required: true
      description: Quoted current resource version from the last ETag response.
      schema:
        type: string
        pattern: '^"[1-9][0-9]*"$'
    CampaignID:
      name: campaignId
      in: path
      required: true
      schema:
        type: string
        format: uuid
    ItemID:
      name: itemId
      in: path
      required: true
      schema:
        type: string
        format: uuid
    GridViewID:
      name: viewId
      in: path
      required: true
      schema:
        type: string
        format: uuid
    ProposalID:
      name: proposalId
      in: path
      required: true
      schema:
        type: string
        format: uuid
    DraftID:
      name: draftId
      in: path
      required: true
      schema:
        type: string
        format: uuid
    MembershipID:
      name: membershipId
      in: path
      required: true
      schema:
        type: string
        format: uuid
    GroupID:
      name: groupId
      in: path
      required: true
      schema:
        type: string
        format: uuid
    TemplateID:
      name: templateId
      in: path
      required: true
      schema:
        type: string
        format: uuid
    TagNamespaceID:
      name: namespaceId
      in: path
      required: true
      schema:
        type: string
        format: uuid
    ControlledTagID:
      name: tagId
      in: path
      required: true
      schema:
        type: string
        format: uuid
    UserID:
      name: userId
      in: path
      required: true
      schema:
        type: string
        format: uuid
    AssignmentID:
      name: assignmentId
      in: path
      required: true
      schema:
        type: string
        format: uuid
    ImportID:
      name: importId
      in: path
      required: true
      schema:
        type: string
        format: uuid
    AuditEventPathID:
      name: auditEventId
      in: path
      required: true
      schema:
        type: string
        format: uuid
    WorkUpdateID:
      name: workUpdateId
      in: path
      required: true
      schema:
        type: string
        format: uuid
  headers:
    RequestID:
      description: Correlation identifier for support and audit trails.
      schema:
        type: string
        minLength: 16
        maxLength: 64
        pattern: "^[A-Za-z0-9_-]+$"
    AuditEventID:
      description: Identifier of the immutable audit event produced by a successful mutation.
      schema:
        type: string
        format: uuid
    ETag:
      description: Quoted optimistic-concurrency resource version.
      schema:
        type: string
        pattern: '^"[1-9][0-9]*"$'
    SetCookie:
      description: Host-only secure browser-session or short-lived OIDC binding cookie.
      schema:
        type: string
        minLength: 1
        maxLength: 4096
  responses:
    BadRequest:
      description: The request is invalid.
      headers:
        X-Request-ID:
          $ref: "#/components/headers/RequestID"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
    Unauthorized:
      description: Authentication is required.
      headers:
        X-Request-ID:
          $ref: "#/components/headers/RequestID"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
    Forbidden:
      description: The authenticated actor is not allowed to perform the operation.
      headers:
        X-Request-ID:
          $ref: "#/components/headers/RequestID"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
    NotFound:
      description: The resource is unavailable or outside the actor's access boundary.
      headers:
        X-Request-ID:
          $ref: "#/components/headers/RequestID"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
    Conflict:
      description: The resource changed or the requested state conflicts with current data.
      headers:
        X-Request-ID:
          $ref: "#/components/headers/RequestID"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
    PayloadTooLarge:
      description: The upload or expanded source exceeds a configured safety limit.
      headers:
        X-Request-ID:
          $ref: "#/components/headers/RequestID"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
    ServiceUnavailable:
      description: At least one required dependency is unavailable.
      headers:
        X-Request-ID:
          $ref: "#/components/headers/RequestID"
        Retry-After:
          description: Suggested retry delay in seconds.
          schema:
            type: integer
            minimum: 1
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
    TooManyRequests:
      description: Authentication is temporarily locked after repeated failures.
      headers:
        X-Request-ID:
          $ref: "#/components/headers/RequestID"
        Retry-After:
          description: Suggested retry delay in seconds.
          schema:
            type: integer
            minimum: 1
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
  schemas:
    Liveness:
      type: object
      additionalProperties: false
      required: [status]
      properties:
        status:
          type: string
          enum: [alive]
    Readiness:
      type: object
      additionalProperties: false
      required: [status, checks]
      properties:
        status:
          type: string
          enum: [ready]
        checks:
          type: object
          additionalProperties:
            type: string
            enum: [ready]
    SystemInfo:
      type: object
      additionalProperties: false
      required: [product, apiVersion, version, commit, builtAt]
      properties:
        product:
          type: string
          enum: [KnownScope]
        apiVersion:
          type: string
          enum: [v1]
        version:
          type: string
          minLength: 1
          maxLength: 128
        commit:
          type: string
          minLength: 1
          maxLength: 128
        builtAt:
          type: string
          minLength: 1
          maxLength: 64
          description: RFC 3339 UTC timestamp, or "unknown" for an unversioned development build.
    AuthenticationMethods:
      type: object
      additionalProperties: false
      required: [oidcEnabled, oidcProviderName, breakGlassEnabled]
      properties:
        oidcEnabled:
          type: boolean
        oidcProviderName:
          type: string
          maxLength: 160
        breakGlassEnabled:
          type: boolean
    BreakGlassLoginRequest:
      type: object
      additionalProperties: false
      required: [username, password]
      properties:
        username:
          type: string
          minLength: 3
          maxLength: 64
        password:
          type: string
          minLength: 20
          maxLength: 1024
          writeOnly: true
        returnTo:
          type: string
          minLength: 1
          maxLength: 1024
          default: /
    AuthenticationRedirect:
      type: object
      additionalProperties: false
      required: [returnTo]
      properties:
        returnTo:
          type: string
          minLength: 1
          maxLength: 1024
    OIDCAuthorization:
      type: object
      additionalProperties: false
      required: [authorizationUrl]
      properties:
        authorizationUrl:
          type: string
          format: uri
    Session:
      type: object
      additionalProperties: false
      required: [tenant, user, authenticationMode]
      properties:
        tenant:
          $ref: "#/components/schemas/Tenant"
        user:
          $ref: "#/components/schemas/User"
        authenticationMode:
          type: string
          enum: [development, oidc, break_glass]
        csrfToken:
          type: string
          minLength: 43
          maxLength: 128
        standardTeamId:
          type: string
          format: uuid
    Tenant:
      type: object
      additionalProperties: false
      required: [id, name, slug, timezone]
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        slug:
          type: string
        timezone:
          type: string
    User:
      type: object
      additionalProperties: false
      required: [id, email, displayName, status, tenantRole]
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        displayName:
          type: string
        status:
          type: string
          enum: [active, disabled]
        tenantRole:
          type: string
          enum: [administrator, member]
    Group:
      type: object
      additionalProperties: false
      required: [id, name, description, isStandardTeam, status, memberCount]
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        description:
          type: string
        isStandardTeam:
          type: boolean
        status:
          type: string
          enum: [active, disabled]
        memberCount:
          type: integer
          minimum: 0
    CreateGroupRequest:
      type: object
      additionalProperties: false
      required: [name]
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 160
        description:
          type: string
          maxLength: 1000
    TagCatalogStatus:
      type: string
      enum: [active, retired]
    TagNamespace:
      type: object
      additionalProperties: false
      required:
        [id, stableKey, label, description, status, version, createdAt, updatedAt]
      properties:
        id:
          type: string
          format: uuid
        stableKey:
          type: string
          pattern: "^[a-z][a-z0-9_]{1,63}$"
        label:
          type: string
          minLength: 1
          maxLength: 160
        description:
          type: string
          maxLength: 1000
        status:
          $ref: "#/components/schemas/TagCatalogStatus"
        version:
          type: integer
          format: int64
          minimum: 1
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    ControlledTag:
      type: object
      additionalProperties: false
      required:
        [id, namespaceId, stableKey, label, description, status, version, createdAt, updatedAt]
      properties:
        id:
          type: string
          format: uuid
        namespaceId:
          type: string
          format: uuid
        stableKey:
          type: string
          pattern: "^[a-z][a-z0-9_]{1,63}$"
        label:
          type: string
          minLength: 1
          maxLength: 160
        description:
          type: string
          maxLength: 1000
        status:
          $ref: "#/components/schemas/TagCatalogStatus"
        version:
          type: integer
          format: int64
          minimum: 1
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    TagCatalog:
      type: object
      additionalProperties: false
      required: [namespaces, tags]
      properties:
        namespaces:
          type: array
          maxItems: 2000
          items:
            $ref: "#/components/schemas/TagNamespace"
        tags:
          type: array
          maxItems: 2000
          items:
            $ref: "#/components/schemas/ControlledTag"
    CreateTagNamespaceRequest:
      type: object
      additionalProperties: false
      required: [stableKey, label]
      properties:
        stableKey:
          type: string
          pattern: "^[a-z][a-z0-9_]{1,63}$"
        label:
          type: string
          minLength: 1
          maxLength: 160
        description:
          type: string
          maxLength: 1000
    UpdateTagNamespaceRequest:
      type: object
      additionalProperties: false
      required: [label, description]
      properties:
        label:
          type: string
          minLength: 1
          maxLength: 160
        description:
          type: string
          maxLength: 1000
    CreateControlledTagRequest:
      type: object
      additionalProperties: false
      required: [stableKey, label]
      properties:
        stableKey:
          type: string
          pattern: "^[a-z][a-z0-9_]{1,63}$"
        label:
          type: string
          minLength: 1
          maxLength: 160
        description:
          type: string
          maxLength: 1000
    UpdateControlledTagRequest:
      type: object
      additionalProperties: false
      required: [label, description, status]
      properties:
        label:
          type: string
          minLength: 1
          maxLength: 160
        description:
          type: string
          maxLength: 1000
        status:
          $ref: "#/components/schemas/TagCatalogStatus"
    CampaignTemplate:
      type: object
      additionalProperties: false
      required: [id, stableKey, name, description, version, isDefault]
      properties:
        id:
          type: string
          format: uuid
        stableKey:
          type: string
        name:
          type: string
        description:
          type: string
        version:
          type: integer
          minimum: 1
        isDefault:
          type: boolean
    CreateCampaignTemplateRequest:
      type: object
      additionalProperties: false
      required: [stableKey, name, fields]
      properties:
        stableKey:
          type: string
          pattern: "^[a-z][a-z0-9_]{1,63}$"
        name:
          type: string
          minLength: 1
          maxLength: 160
        description:
          type: string
          maxLength: 1000
        fields:
          type: array
          description: Complete ordered field set for version 1. It must retain item_name as required, non-null, visible, filterable, sortable short text. Controlled-tag fields require the governed tag catalog.
          minItems: 1
          maxItems: 200
          items:
            $ref: "#/components/schemas/CreateFieldDefinition"
    PublishCampaignTemplateVersionRequest:
      type: object
      additionalProperties: false
      required: [baseVersion, fields]
      properties:
        baseVersion:
          type: integer
          minimum: 1
        fields:
          type: array
          description: Complete ordered replacement field set. It must retain item_name as required, non-null, visible, filterable, sortable short text. Existing versions and Campaign snapshots remain unchanged.
          minItems: 1
          maxItems: 200
          items:
            $ref: "#/components/schemas/CreateFieldDefinition"
    CampaignTemplateVersion:
      type: object
      additionalProperties: false
      required: [templateId, version, fields, publishedAt, publishedBy]
      properties:
        templateId:
          type: string
          format: uuid
        version:
          type: integer
          minimum: 1
        fields:
          type: array
          minItems: 1
          maxItems: 200
          items:
            $ref: "#/components/schemas/CreateFieldDefinition"
        publishedAt:
          type: string
          format: date-time
        publishedBy:
          $ref: "#/components/schemas/UserReference"
    SubjectType:
      type: string
      enum: [user, group]
    CampaignRole:
      type: string
      enum: [owner, worker, viewer]
    AssignableCampaignRole:
      type: string
      enum: [worker, viewer]
    UpsertRoleRequest:
      type: object
      additionalProperties: false
      required: [subjectType, subjectId, role]
      properties:
        subjectType:
          $ref: "#/components/schemas/SubjectType"
        subjectId:
          type: string
          format: uuid
        role:
          $ref: "#/components/schemas/AssignableCampaignRole"
    GlobalRoleAssignment:
      type: object
      additionalProperties: false
      required: [id, subjectType, subjectId, displayName, role, createdAt]
      properties:
        id:
          type: string
          format: uuid
        subjectType:
          $ref: "#/components/schemas/SubjectType"
        subjectId:
          type: string
          format: uuid
        displayName:
          type: string
        email:
          type: string
          format: email
        role:
          $ref: "#/components/schemas/AssignableCampaignRole"
        createdAt:
          type: string
          format: date-time
    LifecycleState:
      type: string
      enum: [draft, active, monitoring, resolved, archived]
    Priority:
      type: string
      enum: [low, medium, high, critical]
    SortDirection:
      type: string
      enum: [asc, desc]
      default: desc
    CampaignSortField:
      type: string
      enum: [title, lifecycle_state, priority, owner, updated_at]
      default: updated_at
    UserReference:
      type: object
      additionalProperties: false
      required: [id, displayName, email]
      properties:
        id:
          type: string
          format: uuid
        displayName:
          type: string
        email:
          type: string
          format: email
    Campaign:
      type: object
      additionalProperties: false
      required:
        - id
        - templateId
        - templateVersion
        - title
        - summary
        - technicalDescription
        - lifecycleState
        - priority
        - owner
        - effectiveRole
        - schemaVersion
        - version
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
          format: uuid
        templateId:
          type: string
          format: uuid
        templateVersion:
          type: integer
          minimum: 1
        title:
          type: string
        summary:
          type: string
        technicalDescription:
          type: string
        lifecycleState:
          $ref: "#/components/schemas/LifecycleState"
        stateBeforeArchive:
          $ref: "#/components/schemas/LifecycleState"
        priority:
          $ref: "#/components/schemas/Priority"
        owner:
          $ref: "#/components/schemas/UserReference"
        effectiveRole:
          $ref: "#/components/schemas/CampaignRole"
        schemaVersion:
          type: integer
          minimum: 1
        version:
          type: integer
          format: int64
          minimum: 1
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        activatedAt:
          type: string
          format: date-time
        resolvedAt:
          type: string
          format: date-time
        archivedAt:
          type: string
          format: date-time
    CampaignPage:
      type: object
      additionalProperties: false
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/Campaign"
        nextCursor:
          type: string
    CreateCampaignRequest:
      type: object
      additionalProperties: false
      required: [title, summary, priority]
      properties:
        templateId:
          type: string
          format: uuid
        title:
          type: string
          minLength: 1
          maxLength: 200
        summary:
          type: string
          minLength: 1
          maxLength: 2000
        technicalDescription:
          type: string
          maxLength: 10000
        priority:
          $ref: "#/components/schemas/Priority"
        initialMembers:
          type: array
          maxItems: 50
          items:
            $ref: "#/components/schemas/UpsertRoleRequest"
    UpdateCampaignRequest:
      type: object
      additionalProperties: false
      minProperties: 1
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 200
        summary:
          type: string
          minLength: 1
          maxLength: 2000
        technicalDescription:
          type: string
          maxLength: 10000
        lifecycleState:
          $ref: "#/components/schemas/LifecycleState"
        priority:
          $ref: "#/components/schemas/Priority"
    CampaignMember:
      type: object
      additionalProperties: false
      required: [subjectType, subjectId, displayName, role, updatedAt]
      properties:
        membershipId:
          type: string
          format: uuid
        subjectType:
          $ref: "#/components/schemas/SubjectType"
        subjectId:
          type: string
          format: uuid
        displayName:
          type: string
        email:
          type: string
          format: email
        role:
          $ref: "#/components/schemas/CampaignRole"
        updatedAt:
          type: string
          format: date-time
    CampaignItemValues:
      type: object
      description: Values keyed by immutable Campaign Field Definition key.
      additionalProperties: true
    CampaignItemWrite:
      type: object
      additionalProperties: false
      required: [schemaVersion, values]
      properties:
        schemaVersion:
          type: integer
          minimum: 1
        values:
          $ref: "#/components/schemas/CampaignItemValues"
    ItemFilterOperator:
      type: string
      enum: [equals, contains, is_empty]
    ItemFilter:
      type: object
      additionalProperties: false
      required: [fieldKey, operator]
      properties:
        fieldKey:
          type: string
          minLength: 2
          maxLength: 64
        operator:
          $ref: "#/components/schemas/ItemFilterOperator"
        value:
          type: string
          maxLength: 10000
    BulkItemReference:
      type: object
      additionalProperties: false
      required: [id, expectedVersion]
      properties:
        id:
          type: string
          format: uuid
        expectedVersion:
          type: integer
          format: int64
          minimum: 1
    BulkUpdateCampaignItemsRequest:
      type: object
      additionalProperties: false
      required: [schemaVersion, items, setValues, unsetFields]
      properties:
        schemaVersion:
          type: integer
          minimum: 1
        items:
          type: array
          minItems: 1
          maxItems: 100
          uniqueItems: true
          items:
            $ref: "#/components/schemas/BulkItemReference"
        setValues:
          $ref: "#/components/schemas/CampaignItemValues"
        unsetFields:
          type: array
          maxItems: 50
          uniqueItems: true
          items:
            type: string
            minLength: 2
            maxLength: 64
    BulkItemOutcomeStatus:
      type: string
      enum: [updated, unchanged, conflict, not_found, invalid]
    BulkItemOutcome:
      type: object
      additionalProperties: false
      required: [id, status]
      properties:
        id:
          type: string
          format: uuid
        status:
          $ref: "#/components/schemas/BulkItemOutcomeStatus"
        version:
          type: integer
          format: int64
          minimum: 1
        detail:
          type: string
          maxLength: 1000
    BulkUpdateCampaignItemsResponse:
      type: object
      additionalProperties: false
      required: [items, updatedCount]
      properties:
        items:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/BulkItemOutcome"
        updatedCount:
          type: integer
          minimum: 0
    BulkArchiveCampaignItemsRequest:
      type: object
      additionalProperties: false
      required: [items]
      properties:
        items:
          type: array
          minItems: 1
          maxItems: 100
          uniqueItems: true
          items:
            $ref: "#/components/schemas/BulkItemReference"
    BulkArchiveItemOutcomeStatus:
      type: string
      enum: [archived, conflict, not_found]
    BulkArchiveItemOutcome:
      type: object
      additionalProperties: false
      required: [id, status]
      properties:
        id:
          type: string
          format: uuid
        status:
          $ref: "#/components/schemas/BulkArchiveItemOutcomeStatus"
        version:
          type: integer
          format: int64
          minimum: 1
        auditEventId:
          type: string
          format: uuid
        detail:
          type: string
          maxLength: 1000
    BulkArchiveCampaignItemsResponse:
      type: object
      additionalProperties: false
      required: [items, archivedCount]
      properties:
        items:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/BulkArchiveItemOutcome"
        archivedCount:
          type: integer
          minimum: 0
    BulkCreateWorkUpdatesRequest:
      type: object
      additionalProperties: false
      required: [items, body, mentionedUserIds]
      properties:
        items:
          type: array
          minItems: 1
          maxItems: 100
          uniqueItems: true
          items:
            $ref: "#/components/schemas/BulkItemReference"
        body:
          type: string
          minLength: 1
          maxLength: 10000
        mentionedUserIds:
          type: array
          maxItems: 25
          uniqueItems: true
          items:
            type: string
            format: uuid
    BulkWorkUpdateOutcomeStatus:
      type: string
      enum: [created, conflict, not_found]
    BulkWorkUpdateOutcome:
      type: object
      additionalProperties: false
      required: [itemId, status]
      properties:
        itemId:
          type: string
          format: uuid
        status:
          $ref: "#/components/schemas/BulkWorkUpdateOutcomeStatus"
        itemVersion:
          type: integer
          format: int64
          minimum: 1
        workUpdateId:
          type: string
          format: uuid
        auditEventId:
          type: string
          format: uuid
        detail:
          type: string
          maxLength: 1000
    BulkCreateWorkUpdatesResponse:
      type: object
      additionalProperties: false
      required: [items, createdCount]
      properties:
        items:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/BulkWorkUpdateOutcome"
        createdCount:
          type: integer
          minimum: 0
    CreateCampaignItemRequest:
      type: object
      additionalProperties: false
      required: [schemaVersion, values]
      properties:
        schemaVersion:
          type: integer
          minimum: 1
        values:
          $ref: "#/components/schemas/CampaignItemValues"
    FieldType:
      type: string
      enum:
        - short_text
        - long_text
        - integer
        - decimal
        - date
        - datetime
        - boolean
        - enum
        - multi_enum
        - url
        - hostname
        - domain
        - ip
        - multi_ip
        - cve_advisory
        - user
        - group
        - controlled_tag
    FieldSensitivity:
      type: string
      enum: [internal, confidential, restricted]
    FieldExportPolicy:
      type: string
      enum: [allowed, redacted, excluded]
    CreateFieldDefinition:
      type: object
      description: >-
        Complete field definition used for a new field or, inside a governed schema
        change set, a metadata-only replacement for an existing stable key. Existing
        fields may change only label, description, sensitivity, and exportPolicy in
        this workflow; storage, validation, stable-key, and grid-behavior changes
        require a migration preview.
      additionalProperties: false
      required:
        - key
        - label
        - type
        - required
        - nullable
        - sensitivity
        - exportPolicy
        - width
        - visible
        - filterable
        - sortable
      properties:
        key:
          type: string
          pattern: "^[a-z][a-z0-9_]{1,63}$"
        label:
          type: string
          minLength: 1
          maxLength: 160
        description:
          type: string
          maxLength: 1000
        type:
          $ref: "#/components/schemas/FieldType"
        tagNamespaceId:
          type: string
          format: uuid
          description: >-
            Required only for controlled_tag fields and prohibited for every
            other type. This namespace binding is immutable after publication.
        required:
          type: boolean
        nullable:
          type: boolean
        allowedValues:
          type: array
          maxItems: 200
          uniqueItems: true
          items:
            type: string
            minLength: 1
            maxLength: 160
        minLength:
          type: integer
          minimum: 0
        maxLength:
          type: integer
          minimum: 1
        minimum:
          type: number
          format: double
        maximum:
          type: number
          format: double
        sensitivity:
          $ref: "#/components/schemas/FieldSensitivity"
        exportPolicy:
          $ref: "#/components/schemas/FieldExportPolicy"
        width:
          type: integer
          minimum: 80
          maximum: 800
        visible:
          type: boolean
        filterable:
          type: boolean
        sortable:
          type: boolean
    FieldDefinition:
      allOf:
        - $ref: "#/components/schemas/CreateFieldDefinition"
        - type: object
          required: [id, schemaVersion, displayOrder, archived]
          properties:
            id:
              type: string
              format: uuid
            schemaVersion:
              type: integer
              minimum: 1
            displayOrder:
              type: integer
              minimum: 0
            archived:
              type: boolean
    FieldDefinitionPage:
      type: object
      additionalProperties: false
      required: [schemaVersion, items]
      properties:
        schemaVersion:
          type: integer
          minimum: 1
        items:
          type: array
          items:
            $ref: "#/components/schemas/FieldDefinition"
    FieldDefinitionMutation:
      type: object
      additionalProperties: false
      required: [definition, schemaVersion, campaignVersion]
      properties:
        definition:
          $ref: "#/components/schemas/FieldDefinition"
        schemaVersion:
          type: integer
          minimum: 1
        campaignVersion:
          type: integer
          format: int64
          minimum: 1
    SchemaProposalStatus:
      type: string
      enum: [pending, approved, rejected]
    SchemaProposalDecisionValue:
      type: string
      enum: [approved, rejected]
    SchemaProposalImpact:
      type: object
      additionalProperties: false
      required: [itemCount, validationFailures, dashboardImpact]
      properties:
        itemCount:
          type: integer
          format: int64
          minimum: 0
        validationFailures:
          type: integer
          minimum: 0
        dashboardImpact:
          type: string
          maxLength: 500
    SchemaProposalDecision:
      type: object
      additionalProperties: false
      required:
        - decision
        - reviewedDefinition
        - reviewedDefinitions
        - fieldOrder
        - resultingFieldIds
        - reason
        - decidedBy
        - decidedAt
      properties:
        decision:
          $ref: "#/components/schemas/SchemaProposalDecisionValue"
        reviewedDefinition:
          deprecated: true
          description: First reviewed definition retained for singleton v1 clients.
          allOf:
            - $ref: "#/components/schemas/CreateFieldDefinition"
        reviewedDefinitions:
          type: array
          description: Complete reviewed additions and metadata-only edits keyed by stable field key.
          minItems: 1
          maxItems: 50
          items:
            $ref: "#/components/schemas/CreateFieldDefinition"
        fieldOrder:
          $ref: "#/components/schemas/SchemaFieldOrder"
        reason:
          type: string
          maxLength: 2000
        decidedBy:
          $ref: "#/components/schemas/UserReference"
        decidedAt:
          type: string
          format: date-time
        resultingSchemaVersion:
          type: integer
          minimum: 1
        resultingFieldId:
          type: string
          format: uuid
          deprecated: true
          description: First resulting Field Definition ID retained for singleton v1 clients.
        resultingFieldIds:
          type: array
          maxItems: 50
          items:
            type: string
            format: uuid
    SchemaProposal:
      type: object
      additionalProperties: false
      required:
        - id
        - campaignId
        - baseSchemaVersion
        - proposedDefinition
        - proposedDefinitions
        - fieldOrder
        - reason
        - impact
        - submittedBy
        - submittedAt
        - status
      properties:
        id:
          type: string
          format: uuid
        campaignId:
          type: string
          format: uuid
        baseSchemaVersion:
          type: integer
          minimum: 1
        proposedDefinition:
          deprecated: true
          description: First proposed definition retained for singleton v1 clients.
          allOf:
            - $ref: "#/components/schemas/CreateFieldDefinition"
        proposedDefinitions:
          type: array
          description: Complete proposed additions and metadata-only edits keyed by stable field key.
          minItems: 1
          maxItems: 50
          items:
            $ref: "#/components/schemas/CreateFieldDefinition"
        fieldOrder:
          $ref: "#/components/schemas/SchemaFieldOrder"
        reason:
          type: string
          minLength: 1
          maxLength: 2000
        impact:
          $ref: "#/components/schemas/SchemaProposalImpact"
        submittedBy:
          $ref: "#/components/schemas/UserReference"
        submittedAt:
          type: string
          format: date-time
        status:
          $ref: "#/components/schemas/SchemaProposalStatus"
        decision:
          $ref: "#/components/schemas/SchemaProposalDecision"
    SchemaProposalPage:
      type: object
      additionalProperties: false
      required: [items]
      properties:
        items:
          type: array
          maxItems: 200
          items:
            $ref: "#/components/schemas/SchemaProposal"
    SubmitSchemaProposalRequest:
      type: object
      additionalProperties: false
      required: [baseSchemaVersion, definition, reason]
      properties:
        baseSchemaVersion:
          type: integer
          minimum: 1
        definition:
          $ref: "#/components/schemas/CreateFieldDefinition"
        reason:
          type: string
          minLength: 1
          maxLength: 2000
    DecideSchemaProposalRequest:
      type: object
      additionalProperties: false
      required: [decision, reason]
      properties:
        decision:
          $ref: "#/components/schemas/SchemaProposalDecisionValue"
        reason:
          type: string
          maxLength: 2000
        reviewedDefinition:
          $ref: "#/components/schemas/CreateFieldDefinition"
        reviewedDefinitions:
          type: array
          minItems: 1
          maxItems: 50
          items:
            $ref: "#/components/schemas/CreateFieldDefinition"
        fieldOrder:
          $ref: "#/components/schemas/SchemaFieldOrder"
    SchemaProposalMutation:
      type: object
      additionalProperties: false
      required: [proposal, schemaVersion, campaignVersion]
      properties:
        proposal:
          $ref: "#/components/schemas/SchemaProposal"
        schemaVersion:
          type: integer
          minimum: 1
        campaignVersion:
          type: integer
          format: int64
          minimum: 1
    SchemaFieldOrder:
      type: array
      minItems: 1
      maxItems: 250
      uniqueItems: true
      items:
        type: string
        pattern: "^[a-z][a-z0-9_]{1,63}$"
    SchemaDraftStatus:
      type: string
      enum: [active, submitted, published]
    SchemaDraft:
      type: object
      additionalProperties: false
      required:
        - id
        - campaignId
        - baseSchemaVersion
        - version
        - definitions
        - fieldOrder
        - reason
        - status
        - author
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
          format: uuid
        campaignId:
          type: string
          format: uuid
        baseSchemaVersion:
          type: integer
          minimum: 1
        version:
          type: integer
          format: int64
          minimum: 1
        definitions:
          type: array
          description: Queued field additions and metadata-only edits; unchanged active fields are omitted.
          maxItems: 50
          items:
            $ref: "#/components/schemas/CreateFieldDefinition"
        fieldOrder:
          $ref: "#/components/schemas/SchemaFieldOrder"
        reason:
          type: string
          maxLength: 2000
        status:
          $ref: "#/components/schemas/SchemaDraftStatus"
        author:
          $ref: "#/components/schemas/UserReference"
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        resultingProposalId:
          type: string
          format: uuid
        resultingSchemaVersion:
          type: integer
          minimum: 1
    SchemaDraftPage:
      type: object
      additionalProperties: false
      required: [items]
      properties:
        items:
          type: array
          maxItems: 1
          items:
            $ref: "#/components/schemas/SchemaDraft"
    SaveSchemaDraftRequest:
      type: object
      additionalProperties: false
      required: [baseSchemaVersion, definitions, fieldOrder, reason]
      properties:
        draftId:
          type: string
          format: uuid
        draftVersion:
          type: integer
          format: int64
          minimum: 1
        baseSchemaVersion:
          type: integer
          minimum: 1
        definitions:
          type: array
          description: Queued field additions and metadata-only edits; unchanged active fields are omitted.
          maxItems: 50
          items:
            $ref: "#/components/schemas/CreateFieldDefinition"
        fieldOrder:
          $ref: "#/components/schemas/SchemaFieldOrder"
        reason:
          type: string
          maxLength: 2000
    FinalizeSchemaDraftRequest:
      type: object
      additionalProperties: false
      required: [draftVersion]
      properties:
        draftVersion:
          type: integer
          format: int64
          minimum: 1
    SchemaDraftMutation:
      type: object
      additionalProperties: false
      required: [draft]
      properties:
        draft:
          $ref: "#/components/schemas/SchemaDraft"
    SchemaPublicationMutation:
      type: object
      additionalProperties: false
      required: [definitions, schemaVersion, campaignVersion]
      properties:
        definitions:
          description: Field Definitions added or metadata-edited by this publication; read the Campaign fields endpoint for the complete canonical schema.
          type: array
          maxItems: 50
          items:
            $ref: "#/components/schemas/FieldDefinition"
        schemaVersion:
          type: integer
          minimum: 1
        campaignVersion:
          type: integer
          format: int64
          minimum: 1
    FieldValueAttribution:
      type: object
      additionalProperties: false
      required: [fieldKey, changedAt, changedBy, sourceType]
      properties:
        fieldKey:
          type: string
        changedAt:
          type: string
          format: date-time
        changedBy:
          $ref: "#/components/schemas/UserReference"
        sourceType:
          type: string
          enum: [web, api, import, connector, system]
        sourceId:
          type: string
          format: uuid
    CampaignItem:
      type: object
      additionalProperties: false
      required:
        - id
        - campaignId
        - values
        - schemaVersion
        - version
        - createdAt
        - updatedAt
        - createdBy
        - updatedBy
        - fieldAttribution
        - workUpdateCount
      properties:
        id:
          type: string
          format: uuid
        campaignId:
          type: string
          format: uuid
        values:
          $ref: "#/components/schemas/CampaignItemValues"
        schemaVersion:
          type: integer
          minimum: 1
        version:
          type: integer
          format: int64
          minimum: 1
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        createdBy:
          $ref: "#/components/schemas/UserReference"
        updatedBy:
          $ref: "#/components/schemas/UserReference"
        fieldAttribution:
          type: array
          items:
            $ref: "#/components/schemas/FieldValueAttribution"
        workUpdateCount:
          type: integer
          format: int64
          minimum: 0
        latestWorkUpdate:
          $ref: "#/components/schemas/WorkUpdatePreview"
    CampaignItemPage:
      type: object
      additionalProperties: false
      required: [items, totalCount]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/CampaignItem"
        nextCursor:
          type: string
        totalCount:
          type: integer
          format: int64
          minimum: 0
          description: Exact number of active Campaign Items matching the current filter.
    CampaignItemExportFormat:
      type: string
      enum: [csv, json]
    CampaignItemExportRequest:
      type: object
      additionalProperties: false
      required: [format, sortBy, sortDirection]
      properties:
        format:
          $ref: "#/components/schemas/CampaignItemExportFormat"
        fieldKeys:
          type: array
          maxItems: 100
          uniqueItems: true
          description: >-
            Active stable field keys to include. Omitted or empty selects every
            exportable active field in canonical schema order.
          items:
            type: string
            pattern: "^[a-z][a-z0-9_]{1,63}$"
        sortBy:
          type: string
          minLength: 2
          maxLength: 64
          description: Stable sortable Field Definition key, or updated_at.
        sortDirection:
          $ref: "#/components/schemas/SortDirection"
        filter:
          $ref: "#/components/schemas/ItemFilter"
    CampaignItemExportField:
      type: object
      additionalProperties: false
      required: [key, label, type, redacted]
      properties:
        key:
          type: string
        label:
          type: string
        type:
          $ref: "#/components/schemas/FieldType"
        tagNamespaceId:
          type: string
          format: uuid
          description: Stable namespace identity for a controlled-tag field.
        redacted:
          type: boolean
    CampaignItemExportRow:
      type: object
      additionalProperties: false
      required: [itemId, schemaVersion, values, createdAt, updatedAt, updatedBy]
      properties:
        itemId:
          type: string
          format: uuid
        schemaVersion:
          type: integer
          minimum: 1
        values:
          $ref: "#/components/schemas/CampaignItemValues"
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        updatedBy:
          $ref: "#/components/schemas/UserReference"
    CampaignItemExportDocument:
      type: object
      additionalProperties: false
      required:
        - campaignId
        - schemaVersion
        - exportedAt
        - auditEventId
        - fields
        - items
      properties:
        campaignId:
          type: string
          format: uuid
        schemaVersion:
          type: integer
          minimum: 1
        exportedAt:
          type: string
          format: date-time
        auditEventId:
          type: string
          format: uuid
        fields:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/CampaignItemExportField"
        items:
          type: array
          maxItems: 10000
          items:
            $ref: "#/components/schemas/CampaignItemExportRow"
    GridViewScope:
      type: string
      enum: [personal, team]
    GridViewConfiguration:
      type: object
      additionalProperties: false
      required:
        - sortBy
        - sortDirection
        - fieldOrder
        - visibleFields
        - pinnedFields
      properties:
        sortBy:
          type: string
          minLength: 2
          maxLength: 64
        sortDirection:
          $ref: "#/components/schemas/SortDirection"
        filter:
          $ref: "#/components/schemas/ItemFilter"
        fieldOrder:
          type: array
          maxItems: 250
          uniqueItems: true
          items:
            type: string
            minLength: 2
            maxLength: 64
        visibleFields:
          type: array
          maxItems: 250
          uniqueItems: true
          items:
            type: string
            minLength: 2
            maxLength: 64
        pinnedFields:
          type: array
          maxItems: 250
          uniqueItems: true
          items:
            type: string
            minLength: 2
            maxLength: 64
    GridView:
      type: object
      additionalProperties: false
      required: [id, campaignId, name, scope, configuration, owner, version, createdAt, updatedAt]
      properties:
        id:
          type: string
          format: uuid
        campaignId:
          type: string
          format: uuid
        name:
          type: string
          minLength: 1
          maxLength: 100
        scope:
          $ref: "#/components/schemas/GridViewScope"
        configuration:
          $ref: "#/components/schemas/GridViewConfiguration"
        owner:
          $ref: "#/components/schemas/UserReference"
        version:
          type: integer
          format: int64
          minimum: 1
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    GridViewPage:
      type: object
      additionalProperties: false
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/GridView"
    CreateGridViewRequest:
      type: object
      additionalProperties: false
      required: [name, scope, configuration]
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 100
        scope:
          $ref: "#/components/schemas/GridViewScope"
        configuration:
          $ref: "#/components/schemas/GridViewConfiguration"
    UpdateGridViewRequest:
      type: object
      additionalProperties: false
      required: [name, configuration]
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 100
        configuration:
          $ref: "#/components/schemas/GridViewConfiguration"
    GridViewMutation:
      type: object
      additionalProperties: false
      required: [view]
      properties:
        view:
          $ref: "#/components/schemas/GridView"
    ImportSourceKind:
      type: string
      enum: [csv, xlsx, clipboard]
    ImportState:
      type: string
      enum:
        - uploaded
        - configured
        - dry_run_ready
        - queued
        - running
        - completed
        - completed_with_errors
        - failed
        - cancelled
    ImportEncoding:
      type: string
      enum: [auto, utf-8, utf-16le, utf-16be, windows-1252]
    ImportDelimiter:
      type: string
      enum: [auto, comma, tab, semicolon, pipe]
    ImportHeaderMode:
      type: string
      enum: [auto, row, none]
    ImportShape:
      type: object
      additionalProperties: false
      required: [encoding, delimiter, headerMode]
      properties:
        worksheet:
          type: string
          maxLength: 255
        encoding:
          $ref: "#/components/schemas/ImportEncoding"
        delimiter:
          $ref: "#/components/schemas/ImportDelimiter"
        headerMode:
          $ref: "#/components/schemas/ImportHeaderMode"
        headerRow:
          type: integer
          minimum: 1
          maximum: 20000
        firstDataRow:
          type: integer
          minimum: 1
          maximum: 20000
        lastDataRow:
          type: integer
          minimum: 1
          maximum: 20000
    ImportSheet:
      type: object
      additionalProperties: false
      required: [name, hidden]
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 255
        hidden:
          type: boolean
    ImportSourceRow:
      type: object
      additionalProperties: false
      required: [number, cells]
      properties:
        number:
          type: integer
          minimum: 1
        cells:
          type: array
          maxItems: 200
          items:
            type: string
            maxLength: 32768
    ImportColumn:
      type: object
      additionalProperties: false
      required: [index, name, examples]
      properties:
        index:
          type: integer
          minimum: 0
          maximum: 199
        name:
          type: string
          minLength: 1
          maxLength: 32768
        examples:
          type: array
          maxItems: 3
          items:
            type: string
            maxLength: 32768
        suggestedFieldKey:
          type: string
          pattern: "^[a-z][a-z0-9_]{1,63}$"
    ImportPreview:
      type: object
      additionalProperties: false
      required:
        - sheets
        - warnings
        - detectedEncoding
        - detectedDelimiter
        - resolvedShape
        - columns
        - rows
        - selectedRowCount
      properties:
        sheets:
          type: array
          maxItems: 200
          items:
            $ref: "#/components/schemas/ImportSheet"
        warnings:
          type: array
          maxItems: 20
          items:
            type: string
            maxLength: 500
        detectedEncoding:
          $ref: "#/components/schemas/ImportEncoding"
        detectedDelimiter:
          $ref: "#/components/schemas/ImportDelimiter"
        resolvedShape:
          $ref: "#/components/schemas/ImportShape"
        columns:
          type: array
          maxItems: 200
          items:
            $ref: "#/components/schemas/ImportColumn"
        rows:
          type: array
          maxItems: 25
          items:
            $ref: "#/components/schemas/ImportSourceRow"
        selectedRowCount:
          type: integer
          minimum: 0
          maximum: 20000
    ImportMapping:
      type: object
      additionalProperties: false
      required: [columnIndex, update]
      properties:
        columnIndex:
          type: integer
          minimum: 0
          maximum: 199
        fieldKey:
          type: string
          pattern: "^[a-z][a-z0-9_]{1,63}$"
          description: Omit to ignore this source column.
        update:
          type: boolean
          description: Whether this mapped field may change a matched Item.
    ImportConfiguration:
      type: object
      additionalProperties: false
      required:
        - shape
        - mappings
        - matchFields
        - unmatchedPolicy
        - matchedPolicy
        - blankPolicy
        - multiValuePolicy
        - requireClean
      properties:
        shape:
          $ref: "#/components/schemas/ImportShape"
        mappings:
          type: array
          minItems: 1
          maxItems: 200
          items:
            $ref: "#/components/schemas/ImportMapping"
        matchFields:
          type: array
          maxItems: 3
          uniqueItems: true
          items:
            type: string
            pattern: "^[a-z][a-z0-9_]{1,63}$"
        createDefaults:
          allOf:
            - $ref: "#/components/schemas/CampaignItemValues"
          description: >-
            Typed constants for required fields, keyed by immutable Field Definition key.
            A default is used only when this import creates a new Item and the mapped
            source cell is absent or blank. Defaults never participate in matching and
            never modify an existing Item.
        unmatchedPolicy:
          type: string
          enum: [create, skip]
        matchedPolicy:
          type: string
          enum: [skip, fill_empty, update]
        blankPolicy:
          type: string
          enum: [no_change, clear]
        multiValuePolicy:
          type: string
          enum: [union, replace]
        requireClean:
          type: boolean
    ImportOutcome:
      type: string
      enum: [create, update, unchanged, skip, invalid, duplicate_source_key, ambiguous_match, conflict]
    ImportRowError:
      type: object
      additionalProperties: false
      required: [detail]
      properties:
        columnIndex:
          type: integer
          minimum: 0
          maximum: 199
        columnName:
          type: string
          maxLength: 32768
        fieldKey:
          type: string
          pattern: "^[a-z][a-z0-9_]{1,63}$"
        detail:
          type: string
          minLength: 1
          maxLength: 1000
    ImportValueDiff:
      type: object
      additionalProperties: false
      required: [fieldKey, redacted]
      properties:
        fieldKey:
          type: string
          pattern: "^[a-z][a-z0-9_]{1,63}$"
        before: {}
        after: {}
        redacted:
          type: boolean
    ImportRowResult:
      type: object
      additionalProperties: false
      required: [sourceRow, outcome, changedFields, errors]
      properties:
        sourceRow:
          type: integer
          minimum: 1
        sourceSheet:
          type: string
          maxLength: 255
        outcome:
          $ref: "#/components/schemas/ImportOutcome"
        itemId:
          type: string
          format: uuid
        expectedItemVersion:
          type: integer
          format: int64
          minimum: 1
        changedFields:
          type: array
          maxItems: 250
          uniqueItems: true
          items:
            type: string
            pattern: "^[a-z][a-z0-9_]{1,63}$"
        diff:
          type: array
          maxItems: 250
          items:
            $ref: "#/components/schemas/ImportValueDiff"
        errors:
          type: array
          maxItems: 200
          items:
            $ref: "#/components/schemas/ImportRowError"
    ImportSummary:
      type: object
      additionalProperties: false
      required: [total, create, update, unchanged, skip, invalid, duplicateSourceKey, ambiguousMatch, conflict]
      properties:
        total:
          type: integer
          minimum: 0
        create:
          type: integer
          minimum: 0
        update:
          type: integer
          minimum: 0
        unchanged:
          type: integer
          minimum: 0
        skip:
          type: integer
          minimum: 0
        invalid:
          type: integer
          minimum: 0
        duplicateSourceKey:
          type: integer
          minimum: 0
        ambiguousMatch:
          type: integer
          minimum: 0
        conflict:
          type: integer
          minimum: 0
    ImportDryRun:
      type: object
      additionalProperties: false
      required: [configurationVersion, planHash, summary, rows, previewTruncated]
      properties:
        configurationVersion:
          type: integer
          format: int64
          minimum: 1
        planHash:
          type: string
          pattern: "^[a-f0-9]{64}$"
        summary:
          $ref: "#/components/schemas/ImportSummary"
        rows:
          type: array
          maxItems: 100
          items:
            $ref: "#/components/schemas/ImportRowResult"
        previewTruncated:
          type: boolean
    CampaignImport:
      type: object
      additionalProperties: false
      required:
        - id
        - campaignId
        - sourceKind
        - sourceName
        - sourceSize
        - sourceHash
        - state
        - schemaVersion
        - configurationVersion
        - inspection
        - summary
        - processedRows
        - createdByUserId
        - createdAt
        - updatedAt
        - expiresAt
        - auditEventId
        - version
      properties:
        id:
          type: string
          format: uuid
        campaignId:
          type: string
          format: uuid
        sourceKind:
          $ref: "#/components/schemas/ImportSourceKind"
        sourceName:
          type: string
          minLength: 1
          maxLength: 255
        sourceSize:
          type: integer
          format: int64
          minimum: 1
          maximum: 10485760
        sourceHash:
          type: string
          pattern: "^[a-f0-9]{64}$"
        state:
          $ref: "#/components/schemas/ImportState"
        schemaVersion:
          type: integer
          minimum: 1
        configurationVersion:
          type: integer
          format: int64
          minimum: 0
        inspection:
          $ref: "#/components/schemas/ImportPreview"
        configuration:
          $ref: "#/components/schemas/ImportConfiguration"
        dryRun:
          $ref: "#/components/schemas/ImportDryRun"
        summary:
          $ref: "#/components/schemas/ImportSummary"
        processedRows:
          type: integer
          minimum: 0
        riverJobId:
          type: integer
          format: int64
          minimum: 1
        errorCode:
          type: string
          pattern: "^[a-z][a-z0-9_]{1,63}$"
        errorMessage:
          type: string
          maxLength: 500
        createdByUserId:
          type: string
          format: uuid
        commitActorUserId:
          type: string
          format: uuid
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        expiresAt:
          type: string
          format: date-time
        startedAt:
          type: string
          format: date-time
        completedAt:
          type: string
          format: date-time
        auditEventId:
          type: string
          format: uuid
        version:
          type: integer
          format: int64
          minimum: 1
    CampaignImportPage:
      type: object
      additionalProperties: false
      required: [items]
      properties:
        items:
          type: array
          maxItems: 50
          items:
            $ref: "#/components/schemas/CampaignImport"
    CommitImportRequest:
      type: object
      additionalProperties: false
      required: [configurationVersion]
      properties:
        configurationVersion:
          type: integer
          format: int64
          minimum: 1
    WorkUpdateItemReference:
      type: object
      additionalProperties: false
      required: [id, displayName]
      properties:
        id:
          type: string
          format: uuid
        displayName:
          type: string
          minLength: 1
          maxLength: 300
    WorkUpdate:
      type: object
      additionalProperties: false
      required:
        - id
        - campaignId
        - author
        - mentions
        - currentRevision
        - version
        - createdAt
        - updatedAt
        - lastAuditEventId
        - canEdit
        - canDelete
      properties:
        id:
          type: string
          format: uuid
        campaignId:
          type: string
          format: uuid
        item:
          $ref: "#/components/schemas/WorkUpdateItemReference"
        body:
          description: Plain-text current body. Omitted when the work update is a tombstone.
          type: string
          minLength: 1
          maxLength: 10000
        author:
          $ref: "#/components/schemas/UserReference"
        mentions:
          type: array
          maxItems: 25
          uniqueItems: true
          items:
            $ref: "#/components/schemas/UserReference"
        currentRevision:
          type: integer
          minimum: 1
        version:
          type: integer
          format: int64
          minimum: 1
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        editedAt:
          type: string
          format: date-time
        deletedAt:
          type: string
          format: date-time
        deletedBy:
          $ref: "#/components/schemas/UserReference"
        lastAuditEventId:
          type: string
          format: uuid
        canEdit:
          type: boolean
        canDelete:
          type: boolean
    WorkUpdatePreview:
      type: object
      additionalProperties: false
      required: [id, body, author, createdAt]
      properties:
        id:
          type: string
          format: uuid
        body:
          type: string
          minLength: 1
          maxLength: 10000
        author:
          $ref: "#/components/schemas/UserReference"
        createdAt:
          type: string
          format: date-time
    WorkUpdatePage:
      type: object
      additionalProperties: false
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/WorkUpdate"
        nextCursor:
          type: string
    WorkUpdateRevision:
      type: object
      additionalProperties: false
      required:
        - revisionNumber
        - body
        - editor
        - mentions
        - sourceType
        - auditEventId
        - createdAt
      properties:
        revisionNumber:
          type: integer
          minimum: 1
        body:
          type: string
          minLength: 1
          maxLength: 10000
        editor:
          $ref: "#/components/schemas/UserReference"
        mentions:
          type: array
          maxItems: 25
          uniqueItems: true
          items:
            $ref: "#/components/schemas/UserReference"
        sourceType:
          type: string
          enum: [web, api, system]
        auditEventId:
          type: string
          format: uuid
        createdAt:
          type: string
          format: date-time
    WorkUpdateRevisionPage:
      type: object
      additionalProperties: false
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/WorkUpdateRevision"
        nextCursor:
          type: string
    CreateWorkUpdateRequest:
      type: object
      additionalProperties: false
      required: [body, mentionedUserIds]
      properties:
        itemId:
          type: string
          format: uuid
        body:
          type: string
          minLength: 1
          maxLength: 10000
        mentionedUserIds:
          type: array
          maxItems: 25
          uniqueItems: true
          items:
            type: string
            format: uuid
    UpdateWorkUpdateRequest:
      type: object
      additionalProperties: false
      required: [body, mentionedUserIds]
      properties:
        body:
          type: string
          minLength: 1
          maxLength: 10000
        mentionedUserIds:
          type: array
          maxItems: 25
          uniqueItems: true
          items:
            type: string
            format: uuid
    WorkUpdateMutation:
      type: object
      additionalProperties: false
      required: [workUpdate]
      properties:
        workUpdate:
          $ref: "#/components/schemas/WorkUpdate"
    WorkUpdateParticipantPage:
      type: object
      additionalProperties: false
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/UserReference"
    ActivityKind:
      type: string
      enum: [audit_event, work_update]
    ActivityEntry:
      type: object
      additionalProperties: false
      required: [kind, occurredAt]
      description: >-
        Exactly one of auditEvent or workUpdate is present and matches kind.
      properties:
        kind:
          $ref: "#/components/schemas/ActivityKind"
        occurredAt:
          type: string
          format: date-time
        auditEvent:
          $ref: "#/components/schemas/AuditEvent"
        workUpdate:
          $ref: "#/components/schemas/WorkUpdate"
    ActivityPage:
      type: object
      additionalProperties: false
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/ActivityEntry"
        nextCursor:
          type: string
    AuditEventView:
      type: string
      enum: [activity, operations, access, all]
      default: activity
    AuditEventClassification:
      type: string
      enum: [activity, operations, access]
    AuditEvent:
      type: object
      additionalProperties: false
      required:
        - id
        - actor
        - action
        - label
        - classifications
        - targetType
        - targetId
        - requestId
        - sourceChannel
        - outcome
        - summary
        - changeSummary
        - occurredAt
        - hash
      properties:
        id:
          type: string
          format: uuid
        actor:
          $ref: "#/components/schemas/UserReference"
        action:
          type: string
        label:
          type: string
          minLength: 1
          maxLength: 120
        classifications:
          type: array
          minItems: 1
          maxItems: 3
          uniqueItems: true
          items:
            $ref: "#/components/schemas/AuditEventClassification"
        targetType:
          type: string
        targetId:
          type: string
          format: uuid
        requestId:
          type: string
        sourceChannel:
          type: string
          enum: [web, api, system]
        outcome:
          type: string
          enum: [success, denied, failure]
        summary:
          type: string
          minLength: 1
          maxLength: 300
        changeSummary:
          type: object
          additionalProperties: true
        occurredAt:
          type: string
          format: date-time
        previousHash:
          type: string
          pattern: "^[a-f0-9]{64}$"
        hash:
          type: string
          pattern: "^[a-f0-9]{64}$"
    AuditChangeValue:
      type: object
      additionalProperties: false
      required: [state]
      properties:
        state:
          type: string
          enum: [missing, value, redacted]
        value:
          description: >-
            Captured JSON value. Present, including as JSON null, when state is
            value; omitted when state is missing or redacted.
    AuditChange:
      type: object
      additionalProperties: false
      required: [key, label, kind, before, after, dynamicField, sensitivity, exportPolicy]
      properties:
        key:
          type: string
        label:
          type: string
        kind:
          type: string
          enum: [scalar, collection, metadata]
        before:
          $ref: "#/components/schemas/AuditChangeValue"
        after:
          $ref: "#/components/schemas/AuditChangeValue"
        added:
          type: array
          maxItems: 250
          items: {}
        removed:
          type: array
          maxItems: 250
          items: {}
        dynamicField:
          type: boolean
        sensitivity:
          type: string
          enum: [internal, confidential, restricted]
        exportPolicy:
          type: string
          enum: [allowed, redacted, excluded]
    AuditFact:
      type: object
      additionalProperties: false
      required: [key, label, value]
      properties:
        key:
          type: string
          minLength: 1
          maxLength: 128
        label:
          type: string
          minLength: 1
          maxLength: 160
        value:
          description: Bounded non-secret operational or resource metadata.
    AuditDetailAvailability:
      type: string
      enum: [captured, facts_only, summary_only]
    AuditEventDetail:
      type: object
      additionalProperties: false
      required: [event, summary, availability, changes, facts, truncated]
      properties:
        event:
          $ref: "#/components/schemas/AuditEvent"
        summary:
          type: string
          minLength: 1
          maxLength: 300
        availability:
          $ref: "#/components/schemas/AuditDetailAvailability"
        changes:
          type: array
          maxItems: 250
          items:
            $ref: "#/components/schemas/AuditChange"
        facts:
          type: array
          maxItems: 32
          items:
            $ref: "#/components/schemas/AuditFact"
        detailHash:
          type: string
          pattern: "^[a-f0-9]{64}$"
        truncated:
          type: boolean
    AuditEventPage:
      type: object
      additionalProperties: false
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/AuditEvent"
        nextCursor:
          type: string
    Problem:
      type: object
      additionalProperties: false
      required: [type, title, status, detail, requestId]
      properties:
        type:
          type: string
          format: uri
        title:
          type: string
          minLength: 1
          maxLength: 200
        status:
          type: integer
          minimum: 400
          maximum: 599
        detail:
          type: string
          minLength: 1
          maxLength: 1000
        instance:
          type: string
          format: uri-reference
        requestId:
          type: string
          minLength: 16
          maxLength: 64
          pattern: "^[A-Za-z0-9_-]+$"
