openapi: 3.2.0
info:
  title: Vulcan API
  version: 2.4.1
  description: |
    REST API for Vulcan — STIG-ready security guidance authoring platform.
    All endpoints require session-based authentication via cookie unless noted otherwise.
    Mutation endpoints return a canonical `{toast: {title, message, variant}}` object.
  contact:
    name: MITRE SAF Team
    url: https://github.com/mitre/vulcan
  license:
    name: Apache-2.0
    url: https://www.apache.org/licenses/LICENSE-2.0
servers:
  - url: /
    description: Same-origin (session cookie auth)
security:
  - cookieAuth: []
  - tokenAuth: []
tags:
  - name: Projects
    description: Project CRUD, export, import, and member management
  - name: Components
    description: Component CRUD, spreadsheet import, export, and locking
  - name: Rules
    description: Rule CRUD, revert, section locks, and satisfaction relationships
  - name: Reviews
    description: Review lifecycle — create, triage, adjudicate, withdraw, admin actions
  - name: Reactions
    description: Thumbs up/down reactions on comment reviews
  - name: Memberships
    description: Project and component membership management
  - name: Search
    description: Global search across projects, components, rules, SRGs, and STIGs
  - name: Users
    description: User management and admin operations
  - name: Benchmarks
    description: SRG and STIG upload, listing, export, and deletion
  - name: Auth
    description: Session authentication — login, logout, current user identity
  - name: Personal Access Tokens
    description: Personal Access Token CRUD and admin revocation
  - name: System
    description: Version and health check endpoints
paths:
  /api/auth/me:
    get:
      operationId: getAuthMe
      tags:
        - Auth
      summary: Current authenticated user identity
      description: Returns the authenticated user's identity, admin status, and provider. Used by the SPA on every page load to determine auth state, populate the navbar, and guard routes. Returns 401 when not authenticated.
      responses:
        '200':
          description: Authenticated user identity
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CurrentUserResponse'
              examples:
                regular_user:
                  summary: Non-admin local user
                  value:
                    id: 42
                    name: Jane Doe
                    email: jane@example.com
                    admin: false
                    provider: null
                admin_user:
                  summary: Admin user via OIDC
                  value:
                    id: 1
                    name: Admin
                    email: admin@example.com
                    admin: true
                    provider: oidc
        '401':
          $ref: '#/components/responses/Unauthorized'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /api/auth/login:
    post:
      operationId: postAuthLogin
      tags:
        - Auth
      security: []
      summary: Authenticate with email and password
      description: Creates a session for local (email/password) authentication. Returns the authenticated user identity on success. Sets a session cookie for subsequent requests. OIDC and LDAP providers use their own OAuth callback flows.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - email
                - password
              properties:
                email:
                  type: string
                  format: email
                  description: User's login email address.
                  example: jane@example.com
                password:
                  type: string
                  format: password
                  description: User's password.
                  example: ••••••••••••••••
            examples:
              login:
                summary: Local login
                value:
                  email: jane@example.com
                  password: S3cure!#Pass001
      responses:
        '200':
          description: Authentication successful — session created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CurrentUserResponse'
              examples:
                success:
                  summary: Successful login
                  value:
                    id: 42
                    name: Jane Doe
                    email: jane@example.com
                    admin: false
                    provider: null
        '401':
          description: Invalid email or password, or the account is temporarily locked after too many failed attempts (RFC 9457 problem details).
          content:
            application/problem+json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - type
                  - title
                  - status
                  - detail
                properties:
                  type:
                    type: string
                    description: Stable machine identifier for the error class.
                    example: /docs/api/errors#invalid_credentials
                  title:
                    type: string
                    description: Short human summary of the error class.
                    example: Invalid credentials
                  status:
                    type: integer
                    description: HTTP status code, repeated in the body.
                    example: 401
                  detail:
                    type: string
                    description: Occurrence-specific human explanation.
                    example: The email or password is incorrect.
              examples:
                invalid_credentials:
                  summary: Bad credentials
                  value:
                    type: /docs/api/errors#invalid_credentials
                    title: Invalid credentials
                    status: 401
                    detail: The email or password is incorrect.
                account_locked:
                  summary: Account locked after too many failed attempts
                  value:
                    type: /docs/api/errors#account_locked
                    title: Account locked
                    status: 401
                    detail: This account is temporarily locked due to too many failed sign-in attempts. Please try again later or reset your password.
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /api/auth/logout:
    delete:
      operationId: deleteAuthLogout
      tags:
        - Auth
      summary: Sign out and destroy session
      description: Destroys the current session. Subsequent requests require re-authentication. Returns a confirmation message.
      responses:
        '200':
          description: Session destroyed
          content:
            application/json:
              schema:
                type: object
                required:
                  - message
                properties:
                  message:
                    type: string
                    description: Confirmation message.
                    example: Signed out successfully
              examples:
                success:
                  summary: Signed out
                  value:
                    message: Signed out successfully
        '401':
          $ref: '#/components/responses/Unauthorized'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /api/navigation:
    get:
      operationId: getNavigation
      tags:
        - System
      summary: App shell navigation data
      description: Returns navbar links, pending access request notifications, and locked user alerts for the authenticated user. Used by the SPA app shell on every page load after authentication.
      responses:
        '200':
          description: Navigation data for app shell
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NavigationResponse'
              examples:
                admin:
                  summary: Admin user with pending requests
                  value:
                    nav_links:
                      - icon: folder2-open
                        name: Projects
                        link: /projects
                      - icon: patch-check-fill
                        name: Released Components
                        link: /components
                    access_requests:
                      - id: 1
                        user:
                          id: 42
                          name: Jane Doe
                          email: jane@example.com
                        project:
                          id: 7
                          name: RHEL 9 STIG
                    locked_users:
                      - id: 99
                        name: Locked User
                        email: locked@example.com
                regular:
                  summary: Non-admin user
                  value:
                    nav_links:
                      - icon: folder2-open
                        name: Projects
                        link: /projects
                    access_requests: []
                    locked_users: []
        '401':
          $ref: '#/components/responses/Unauthorized'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /api/settings:
    get:
      operationId: getSettings
      tags:
        - System
      security: []
      summary: Public pre-auth UI configuration
      description: 'Returns application settings needed before authentication: banner, consent modal, auth provider flags, password policy, and registration status. No authentication required — the login page and consent banner need this data before the user signs in.'
      responses:
        '200':
          description: Public settings for SPA pre-auth UI
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SettingsResponse'
              examples:
                default:
                  summary: Typical deployment
                  value:
                    banner:
                      enabled: true
                      text: UNCLASSIFIED
                      background_color: '#007a33'
                      text_color: '#ffffff'
                    consent:
                      enabled: false
                      version: 1
                      title: Terms of Use
                      content: ''
                      ttl: 0
                    local_login:
                      enabled: true
                    user_registration:
                      enabled: true
                    ldap:
                      enabled: false
                      title: null
                    oidc:
                      enabled: false
                      title: null
                    smtp:
                      enabled: false
                    password:
                      min_length: 15
                      min_uppercase: 2
                      min_lowercase: 2
                      min_number: 2
                      min_special: 2
                    lockout:
                      enabled: true
                      maximum_attempts: 3
                      last_attempt_warning: true
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /api/version:
    get:
      operationId: getVersion
      tags:
        - System
      security: []
      summary: Application version and metadata
      description: Returns the application name, version, Rails version, Ruby version, and environment. No authentication required — used by monitoring tools, deployment verification scripts, and the frontend health check.
      responses:
        '200':
          description: Version and runtime metadata
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VersionResponse'
              examples:
                production:
                  summary: Typical production response
                  value:
                    name: Vulcan
                    version: 2.4.1
                    rails: 8.1.3.1
                    ruby: 3.4.10
                    environment: production
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /api/search/global:
    get:
      operationId: globalSearch
      tags:
        - Search
      summary: Search across all resource types
      description: Searches projects, components, rules, SRGs, and STIGs by name or title. Results are grouped by type and limited per group. Project results cover everything the caller can discover (memberships plus discoverable projects); component and rule content is served only from the caller's memberships and released components; SRG/STIG results and the srg_rules requirement catalog are instance-global. The rules group covers both document kinds — stig rules and authored SRG requirements — and can be scoped to a specific component via the component_id parameter. Requires authentication — returns 401 if not signed in.
      parameters:
        - $ref: '#/components/parameters/SearchQuery'
        - name: limit
          in: query
          description: Maximum results per resource type.
          schema:
            type: integer
            minimum: 1
            maximum: 20
            default: 5
          example: 5
        - name: component_id
          in: query
          description: Scope rule search to a specific component.
          schema:
            type: integer
          example: 29
      responses:
        '200':
          description: Search results grouped by type
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GlobalSearchResponse'
              examples:
                results:
                  summary: Search for "container"
                  value:
                    projects:
                      - id: 4
                        name: Container Platform
                    components:
                      - id: 29
                        name: Container SRG
                    rules: []
                    stig_rules: []
                    srg_rules: []
                    srgs:
                      - id: 1
                        title: Container Platform Security Requirements Guide
                    stigs: []
        '401':
          $ref: '#/components/responses/Unauthorized'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /api/users/search:
    get:
      operationId: searchUsers
      tags:
        - Search
      summary: Search users by name or email
      description: Searches user accounts by name or email address. Used by the membership assignment UI to find users to add to projects. Requires authentication.
      parameters:
        - $ref: '#/components/parameters/SearchQuery'
        - name: membership_type
          in: query
          required: true
          description: Type of resource to search members for.
          schema:
            type: string
            enum:
              - Project
              - Component
          example: Project
        - name: membership_id
          in: query
          required: true
          description: ID of the project or component to search members for.
          schema:
            type: integer
          example: 1
        - name: scope
          in: query
          required: false
          description: Search scope. Default searches non-members (for "add member" flow, admin only). "members" searches existing members (for PoC selection, any member).
          schema:
            type: string
            enum:
              - members
          example: members
        - name: limit
          in: query
          required: false
          description: Maximum number of results (1-25, default 10).
          schema:
            type: integer
            minimum: 1
            maximum: 25
          example: 10
      responses:
        '200':
          description: Matching users
          content:
            application/json:
              schema:
                type: object
                required:
                  - users
                additionalProperties: false
                properties:
                  users:
                    type: array
                    description: Matching users (id, name, email only — minimal shape for dropdowns).
                    items:
                      type: object
                      additionalProperties: false
                      required:
                        - id
                        - name
                        - email
                      properties:
                        id:
                          type: integer
                          description: User ID.
                          example: 42
                        name:
                          type:
                            - string
                            - 'null'
                          description: Display name.
                          example: Jane Doe
                        email:
                          type: string
                          format: email
                          description: Email address.
                          example: jane.doe@example.org
              examples:
                results:
                  summary: Search for "jane"
                  value:
                    users:
                      - id: 42
                        name: Jane Doe
                        email: jane.doe@example.org
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /api/projects:
    get:
      operationId: listApiProjects
      tags:
        - Projects
      summary: List projects with pagination, search, and sorting
      description: Paginated project listing for SPA consumption. Supports substring search over name and description, whitelist-validated sorting, and pagy-backed pagination (default 25 per page, maximum 100). Unknown sort fields are silently ignored. Requires authentication. Returns 400 when the requested page is out of range. Timestamps on this endpoint are ISO 8601 (render_as_json path), unlike the string-rendered project pages.
      parameters:
        - name: q
          in: query
          required: false
          description: Case-insensitive substring filter matched against project name and description.
          schema:
            type: string
          example: Photon
        - name: sort
          in: query
          required: false
          description: Sort field. Unknown fields are silently ignored.
          schema:
            type: string
            enum:
              - name
              - created_at
              - updated_at
          example: name
        - name: order
          in: query
          required: false
          description: Sort direction. Defaults to asc when sort is applied.
          schema:
            type: string
            enum:
              - asc
              - desc
          example: asc
        - name: page
          in: query
          required: false
          description: Page number (1-based). Out-of-range pages return 400.
          schema:
            type: integer
          example: 1
        - name: per_page
          in: query
          required: false
          description: Records per page (default 25, capped at 100).
          schema:
            type: integer
          example: 25
      responses:
        '200':
          description: Paginated project rows
          content:
            application/json:
              schema:
                type: object
                required:
                  - rows
                  - pagination
                additionalProperties: false
                properties:
                  rows:
                    type: array
                    description: Projects on the current page.
                    items:
                      $ref: '#/components/schemas/ProjectSummary'
                  pagination:
                    $ref: '#/components/schemas/PaginationMeta'
              examples:
                first_page:
                  summary: First page of projects
                  value:
                    rows:
                      - id: 34
                        name: Photon 3
                        description: null
                        visibility: discoverable
                        memberships_count: 2
                        admin_name: Demo Admin
                        admin_email: admin@example.org
                        created_at: '2026-05-30T14:06:03.797Z'
                        updated_at: '2026-05-30T14:06:28.292Z'
                    pagination:
                      page: 1
                      per_page: 25
                      total: 1
        '400':
          description: Requested page is out of range (RFC 9457 problem details)
          content:
            application/problem+json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - type
                  - title
                  - status
                  - detail
                properties:
                  type:
                    type: string
                    description: Stable machine identifier for the error class.
                    example: /docs/api/errors#page_out_of_range
                  title:
                    type: string
                    description: Short human summary of the error class.
                    example: Page out of range
                  status:
                    type: integer
                    description: HTTP status code, repeated in the body.
                    example: 400
                  detail:
                    type: string
                    description: Error message naming the valid page range.
                    example: Page 9999 is out of range (1..2)
              examples:
                page_out_of_range:
                  summary: Page beyond the last available page
                  value:
                    type: /docs/api/errors#page_out_of_range
                    title: Page out of range
                    status: 400
                    detail: Page 9999 is out of range (1..2)
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/srgs/latest:
    get:
      operationId: latestSrgs
      tags:
        - Benchmarks
      summary: List the latest release of each SRG
      description: Returns one row per SRG — its numerically highest V{major}R{minor} release — for dropdown population. Public reference data, no authentication required. The q parameter filters SRGs by case-insensitive substring with abbreviation expansion (GPOS matches General Purpose Operating System); queries shorter than 2 characters return no rows.
      security: []
      parameters:
        - name: q
          in: query
          required: false
          description: SRG filter — case-insensitive substring matched against name, title, and srg_id, with abbreviation expansion (minimum 2 characters).
          schema:
            type: string
          example: GPOS
      responses:
        '200':
          description: Latest release of each SRG, ordered by title
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BenchmarkLatestResponse'
              examples:
                latest:
                  summary: Latest SRGs
                  value:
                    rows:
                      - id: 42
                        srg_id: General_Purpose_Operating_System
                        title: General Purpose Operating System Security Requirements Guide
                        version: V3R3
                        name: General Purpose Operating System - Ver 3, Rel 3
                      - id: 43
                        srg_id: Web_Server_SRG
                        title: Web Server Security Requirements Guide
                        version: V4R4
                        name: Web Server SRG - Ver 4, Rel 4
  /api/srgs/{id}/stats:
    get:
      operationId: srgStats
      tags:
        - Benchmarks
      summary: Get rule counts and component usage for an SRG
      description: Returns the SRG's rule count, severity breakdown, and which components are based on it. Usage is scoped to components the caller can see (member projects or released) — the count and list use the same scope, so hidden usage is never revealed. Requires authentication.
      parameters:
        - name: id
          in: path
          required: true
          description: Numeric ID of the SRG.
          schema:
            type: integer
          example: 3
      responses:
        '200':
          description: SRG stats with caller-scoped usage
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BenchmarkStatsResponse'
              examples:
                stats:
                  summary: SRG stats
                  value:
                    rule_count: 250
                    severity_counts:
                      high: 30
                      medium: 200
                      low: 20
                    usage:
                      count: 2
                      components:
                        - id: 38
                          name: RHEL 9 Hardened Baseline
                          project_id: 7
                          project_name: RHEL Hardening
                        - id: 41
                          name: Photon OS 5 Baseline
                          project_id: 9
                          project_name: Photon OS 5 Hardening
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/stigs/latest:
    get:
      operationId: latestStigs
      tags:
        - Benchmarks
      summary: List the latest release of each STIG
      description: Returns one row per STIG — its numerically highest V{major}R{minor} release — for dropdown population. Public reference data, no authentication required. The q parameter filters STIGs by case-insensitive substring with abbreviation expansion (RHEL matches Red Hat Enterprise Linux); queries shorter than 2 characters return no rows.
      security: []
      parameters:
        - name: q
          in: query
          required: false
          description: STIG filter — case-insensitive substring matched against name, title, and stig_id, with abbreviation expansion (minimum 2 characters).
          schema:
            type: string
          example: RHEL
      responses:
        '200':
          description: Latest release of each STIG, ordered by title
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BenchmarkLatestResponse'
              examples:
                latest:
                  summary: Latest STIGs
                  value:
                    rows:
                      - id: 7
                        stig_id: RHEL_9_STIG
                        title: Red Hat Enterprise Linux 9 Security Technical Implementation Guide
                        version: V2R7
                        name: RHEL 9 STIG - Ver 2, Rel 7
  /api/stigs/{id}/stats:
    get:
      operationId: stigStats
      tags:
        - Benchmarks
      summary: Get rule counts for a STIG
      description: Returns the STIG's rule count and severity breakdown. Pure reference data — no usage section (components are based on SRGs, not STIGs) — and public like the rest of the STIG catalog.
      security: []
      parameters:
        - name: id
          in: path
          required: true
          description: Numeric ID of the STIG.
          schema:
            type: integer
          example: 4
      responses:
        '200':
          description: STIG rule counts and severity breakdown
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BenchmarkStatsResponse'
              examples:
                stats:
                  summary: STIG stats
                  value:
                    rule_count: 380
                    severity_counts:
                      high: 40
                      medium: 300
                      low: 40
        '404':
          $ref: '#/components/responses/NotFound'
  /api/components/latest:
    get:
      operationId: latestComponents
      tags:
        - Benchmarks
      summary: List the latest released component per prefix
      description: Returns one released component per prefix — the numerically highest version/release pair — for dropdown population. A component is a STIG in progress; released ones are instance-wide reference data for any authenticated user. Unreleased drafts never appear. The q parameter filters components by case-insensitive substring with abbreviation expansion (RHEL matches Red Hat Enterprise Linux); queries shorter than 2 characters return no rows.
      parameters:
        - name: q
          in: query
          required: false
          description: Component filter — case-insensitive substring matched against name, prefix, and title, with abbreviation expansion (minimum 2 characters).
          schema:
            type: string
          example: RHEL-09
      responses:
        '200':
          description: Latest released component per prefix, ordered by prefix
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ComponentLatestResponse'
              examples:
                latest:
                  summary: Latest released components
                  value:
                    rows:
                      - id: 38
                        prefix: RHEL-09
                        name: RHEL 9 Hardened Baseline
                        title: Red Hat Enterprise Linux 9
                        version: 2
                        release: 1
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/components/{id}/summary:
    get:
      operationId: componentSummary
      tags:
        - Components
      summary: Get a lightweight component summary
      description: 'Returns the component header — identity, counts, SRG info, the caller''s effective permissions, and the serialized comment-phase state machine — without the heavy rules/reviews/histories arrays. Access matches the component show rules: released components are readable by any authenticated user; unreleased ones require viewer permission.'
      parameters:
        - name: id
          in: path
          required: true
          description: Numeric ID of the component.
          schema:
            type: integer
          example: 38
      responses:
        '200':
          description: Component summary with phase state
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ComponentSummaryResponse'
              examples:
                open_period:
                  summary: Component in an open comment period
                  value:
                    id: 38
                    name: RHEL 9 Hardened Baseline
                    prefix: RHEL-09
                    document_type: stig
                    title: Red Hat Enterprise Linux 9
                    version: 2
                    release: 1
                    released: false
                    project_id: 7
                    component_id: null
                    security_requirements_guide_id: 3
                    based_on_title: General Purpose Operating System Security Requirements Guide
                    based_on_version: V3R3
                    rules_count: 203
                    memberships_count: 4
                    severity_counts:
                      high: 20
                      medium: 173
                      low: 10
                    pending_comment_count: 5
                    effective_permissions: viewer
                    updated_at: '2026-07-10T14:07:37.142Z'
                    comment_phase: open
                    closed_reason: null
                    comment_period_starts_at: '2026-07-01T00:00:00.000Z'
                    comment_period_ends_at: '2026-07-15T00:00:00.000Z'
                    accepting_new_comments: true
                    triaging_active: true
                    frozen_for_writes: false
                    comment_period_days_remaining: 5
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/components/{id}/stats:
    get:
      operationId: componentStats
      tags:
        - Components
      summary: Get rule statistics for a component
      description: 'Returns rule counts by status and severity plus completion and lock percentages, computed as SQL aggregates. Access matches the component show rules: released components are readable by any authenticated user; unreleased ones require viewer permission.'
      parameters:
        - name: id
          in: path
          required: true
          description: Numeric ID of the component.
          schema:
            type: integer
          example: 38
      responses:
        '200':
          description: Component rule statistics
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ComponentStatsResponse'
              examples:
                stats:
                  summary: Component stats
                  value:
                    document_type: stig
                    rules_by_status:
                      not_yet_determined: 50
                      applicable_configurable: 120
                      applicable_inherently_meets: 15
                      applicable_does_not_meet: 8
                      not_applicable: 10
                    rules_by_severity:
                      high: 20
                      medium: 173
                      low: 10
                    rule_count: 203
                    completion_pct: 75.4
                    lock_pct: 12.3
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/components/{id}/workflow_state:
    get:
      operationId: componentWorkflowState
      tags:
        - Components
      summary: Get workflow readiness for a component
      description: 'Returns where the component stands across the authoring, lock, review, comment, triage, and export stages — SQL-aggregated counts plus the comment-phase write-guard booleans. Access matches the component show rules: released components are readable by any authenticated user; unreleased ones require viewer permission.'
      parameters:
        - name: id
          in: path
          required: true
          description: Numeric ID of the component.
          schema:
            type: integer
          example: 38
      responses:
        '200':
          description: Component workflow state
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ComponentWorkflowStateResponse'
              examples:
                in_progress:
                  summary: Component mid-authoring with an open comment period
                  value:
                    document_type: stig
                    authoring:
                      rules_total: 203
                      rules_determined: 153
                    locks:
                      locked: 25
                      total: 203
                      all_locked: false
                    reviews:
                      under_review: 4
                    comment:
                      phase: open
                      accepting_new_comments: true
                      triaging_active: true
                      frozen_for_writes: false
                      pending_comments: 3
                    triage:
                      pending: 3
                      awaiting_adjudication: 1
                    export:
                      released: false
                      releasable: false
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/components/{id}/triage_summary:
    get:
      operationId: componentTriageSummary
      tags:
        - Components
      summary: Get triage summary for a component
      description: 'Returns top-level comment counts per triage status plus the adjudication percentage — rule-attached and component-attached comments both count. Access matches the component show rules: released components are readable by any authenticated user; unreleased ones require viewer permission.'
      parameters:
        - name: id
          in: path
          required: true
          description: Numeric ID of the component.
          schema:
            type: integer
          example: 38
      responses:
        '200':
          description: Component triage summary
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TriageSummaryResponse'
              examples:
                summary:
                  summary: Component with comments in triage
                  value:
                    by_triage_status:
                      pending: 3
                      concur: 1
                      concur_with_comment: 0
                      non_concur: 0
                      duplicate: 0
                      informational: 1
                      needs_clarification: 0
                      withdrawn: 0
                      addressed_by: 0
                    total: 5
                    adjudicated: 1
                    adjudication_pct: 20
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/projects/{id}/stats:
    get:
      operationId: projectStats
      tags:
        - Projects
      summary: Get rule statistics for a project
      description: Returns rule statistics aggregated across every component in the project plus a per-component breakdown, computed from grouped SQL queries. Requires viewer permission on the project.
      parameters:
        - name: id
          in: path
          required: true
          description: Numeric ID of the project.
          schema:
            type: integer
          example: 7
      responses:
        '200':
          description: Project rule statistics with per-component breakdown
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectStatsResponse'
              examples:
                stats:
                  summary: Project with two components
                  value:
                    aggregate:
                      rules_by_status_by_type:
                        stig:
                          not_yet_determined: 51
                          applicable_configurable: 121
                          applicable_inherently_meets: 15
                          applicable_does_not_meet: 8
                          not_applicable: 10
                        srg:
                          not_yet_determined: 2
                          applicable: 1
                          not_applicable: 0
                      rules_by_severity:
                        high: 21
                        medium: 176
                        low: 11
                      rule_count: 208
                      completion_pct: 74.5
                      lock_pct: 12
                    components:
                      - id: 38
                        name: RHEL 9 Hardened Baseline
                        prefix: RHEL-09
                        document_type: stig
                        rule_count: 203
                        completion_pct: 75.4
                        lock_pct: 12.3
                      - id: 41
                        name: Photon OS 5 Baseline
                        prefix: PHTN-50
                        document_type: srg
                        rule_count: 5
                        completion_pct: 40
                        lock_pct: 0
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/projects/{id}/triage_summary:
    get:
      operationId: projectTriageSummary
      tags:
        - Projects
      summary: Get triage summary for a project
      description: Returns triage metrics aggregated across all of the project's components — top-level comment counts per triage status plus the adjudication percentage. Requires viewer permission on the project.
      parameters:
        - name: id
          in: path
          required: true
          description: Numeric ID of the project.
          schema:
            type: integer
          example: 7
      responses:
        '200':
          description: Project-wide triage summary
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TriageSummaryResponse'
              examples:
                summary:
                  summary: Project with comments in triage
                  value:
                    by_triage_status:
                      pending: 3
                      concur: 1
                      concur_with_comment: 0
                      non_concur: 0
                      duplicate: 0
                      informational: 1
                      needs_clarification: 0
                      withdrawn: 0
                      addressed_by: 0
                    total: 5
                    adjudicated: 1
                    adjudication_pct: 20
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /search/rules:
    get:
      operationId: searchRulesLegacy
      tags:
        - Search
      summary: Legacy requirement search by SRG version
      description: 'Searches requirement rows of both document kinds — stig rules and authored SRG requirements — by version identifier. Scoped to components the user can access: project or component membership, or released components; admins are unrestricted. Returns compact tuples [id, rule_id, component_id, prefix]. Requires authentication. This is a legacy search endpoint — prefer /api/search/global for new integrations.'
      parameters:
        - name: q
          in: query
          required: true
          description: SRG version identifier to search for.
          schema:
            type: string
          example: SRG-OS-000001-GPOS-00001
      responses:
        '200':
          description: Matching rules as compact tuples
          content:
            application/json:
              schema:
                type: object
                required:
                  - rules
                properties:
                  rules:
                    type: array
                    description: Each item is a tuple [rule_id, rule_id_string, component_id, prefix].
                    items:
                      type: array
                      items: {}
              examples:
                results:
                  summary: Rules matching SRG version
                  value:
                    rules:
                      - - 1786
                        - '000001'
                        - 1
                        - PHOS-03
                      - - 2500
                        - '000001'
                        - 5
                        - CNTR-01
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /search/components:
    get:
      operationId: searchComponentsLegacy
      tags:
        - Search
      summary: Legacy component search by SRG ID
      description: 'Searches components by the SRG identifier they are based on. Scoped to components the user can access: project or component membership, or released components; admins are unrestricted. Returns compact tuples [id, name]. This is a legacy search endpoint — prefer /api/search/global for new integrations.'
      parameters:
        - name: q
          in: query
          required: true
          description: SRG identifier to search for.
          schema:
            type: string
          example: Container_Platform_SRG
      responses:
        '200':
          description: Matching components as compact tuples
          content:
            application/json:
              schema:
                type: object
                required:
                  - components
                additionalProperties: false
                properties:
                  components:
                    type: array
                    description: Each item is a tuple [id, name].
                    items:
                      type: array
                      items: {}
              examples:
                results:
                  summary: Components matching SRG
                  value:
                    components:
                      - - 1
                        - Photon OS 3
                      - - 8
                        - Container Platform
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /search/projects:
    get:
      operationId: searchProjectsLegacy
      tags:
        - Search
      summary: Legacy project search by SRG ID
      description: Searches projects that have components based on a given SRG. Returns compact tuples [id, name]. This is a legacy search endpoint — prefer /api/search/global for new integrations.
      parameters:
        - name: q
          in: query
          required: true
          description: SRG identifier to search for.
          schema:
            type: string
          example: Container_Platform_SRG
      responses:
        '200':
          description: Matching projects as compact tuples
          content:
            application/json:
              schema:
                type: object
                required:
                  - projects
                additionalProperties: false
                properties:
                  projects:
                    type: array
                    description: Each item is a tuple [id, name].
                    items:
                      type: array
                      items: {}
              examples:
                results:
                  summary: Projects with matching SRG components
                  value:
                    projects:
                      - - 4
                        - Container Platform
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /projects:
    get:
      operationId: listProjects
      tags:
        - Projects
      summary: List accessible projects
      description: Returns all projects the current user can access, including owned projects, member projects, and discoverable projects. Includes membership counts and pending comment counts per project. Requires authentication.
      responses:
        '200':
          description: Projects list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ProjectIndexResponse'
              examples:
                projects:
                  summary: Two accessible projects
                  value:
                    - id: 4
                      name: Container Platform
                      memberships_count: 14
                    - id: 1
                      name: Photon 3
                      memberships_count: 14
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    post:
      operationId: createProject
      tags:
        - Projects
      summary: Create a new project
      description: Creates a new project with the given name and description. The creating user is automatically added as an admin member. Requires authentication.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                project:
                  description: The shared project input surface plus the create-only slack_channel_id convenience field, which merges into the metadata data hash as "Slack Channel ID" and wins over a same-key entry supplied in project_metadata_attributes.
                  allOf:
                    - $ref: '#/components/schemas/ProjectInput'
                    - type: object
                      properties:
                        slack_channel_id:
                          type: string
                          description: Convenience field — stored as the metadata key "Slack Channel ID". Accepted at creation only; use project_metadata_attributes on update.
                          example: C0123456789
            examples:
              create:
                summary: Create a new project
                value:
                  project:
                    name: Red Hat Enterprise Linux 9
                    description: STIG development for RHEL 9
              create_with_metadata:
                summary: Create with metadata in the same call
                value:
                  project:
                    name: Red Hat Enterprise Linux 9
                    description: STIG development for RHEL 9
                    visibility: discoverable
                    project_metadata_attributes:
                      data:
                        POC Name: Jane Doe
                        POC Email: jane@example.com
      responses:
        '200':
          description: Project created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectCreateResponse'
              examples:
                created:
                  summary: Project created
                  value:
                    toast:
                      title: Project created.
                      message:
                        - Successfully created project Red Hat Enterprise Linux 9.
                      variant: success
                    redirect_url: /projects/5
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /projects/{projectId}:
    parameters:
      - $ref: '#/components/parameters/ProjectId'
    get:
      operationId: getProject
      tags:
        - Projects
      summary: Project detail with component list and stats
      description: Returns full project details including components, membership count, comment counts, and project metadata. Requires membership in the project or admin role.
      responses:
        '200':
          description: Project detail with components
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectShowResponse'
              examples:
                project:
                  summary: Photon 3 project detail
                  value:
                    id: 1
                    name: Photon 3
                    memberships_count: 16
                    pending_comment_count: 1
                    components:
                      - id: 1
                        name: Photon OS 3
                        prefix: PHOS-03
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    patch:
      operationId: patchProject
      tags:
        - Projects
      summary: Partial update of project attributes
      description: Partial update — send only changed fields. Updates the project name, description, or visibility. Requires admin role on the project. Returns a canonical toast response.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                project:
                  $ref: '#/components/schemas/ProjectInput'
            examples:
              rename:
                summary: Rename a project
                value:
                  project:
                    name: Container Platform v2
      responses:
        '200':
          description: Project updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                updated:
                  summary: Project renamed
                  value:
                    toast:
                      title: Project updated.
                      message:
                        - Successfully updated project.
                      variant: success
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    put:
      operationId: updateProject
      tags:
        - Projects
      summary: Full replacement of project attributes
      description: Full replacement — all fields required. Updates the project name, description, or visibility. Requires admin role on the project. Returns a canonical toast response.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                project:
                  $ref: '#/components/schemas/ProjectInput'
            examples:
              rename:
                summary: Rename a project
                value:
                  project:
                    name: Container Platform v2
      responses:
        '200':
          description: Project updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                updated:
                  summary: Project renamed
                  value:
                    toast:
                      title: Project updated.
                      message:
                        - Successfully updated project.
                      variant: success
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    delete:
      operationId: deleteProject
      tags:
        - Projects
      summary: Delete a project and all its components
      description: Permanently deletes the project, all its components, rules, reviews, and memberships. Requires admin role on the project. This action cannot be undone. Returns 403 for non-admin users.
      responses:
        '200':
          description: Project deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                deleted:
                  summary: Project deleted
                  value:
                    toast:
                      title: Project deleted.
                      message:
                        - Successfully deleted project.
                      variant: success
        '403':
          $ref: '#/components/responses/Forbidden'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /projects/{projectId}/comments:
    parameters:
      - $ref: '#/components/parameters/ProjectId'
    get:
      operationId: getProjectComments
      tags:
        - Projects
      summary: Aggregated comments across all project components
      description: Returns paginated comments from all components in the project, with triage status counts. Different row shape from component comments — includes component_id/component_name but omits some triage attribution fields. Uses Project#paginated_comments (NOT CommentQueryService).
      parameters:
        - $ref: '#/components/parameters/TriageStatusFilter'
        - $ref: '#/components/parameters/PageParam'
        - $ref: '#/components/parameters/PerPageParam'
        - name: section
          in: query
          description: Filter by requirement section (fixtext, check_content, etc.).
          schema:
            type: string
          example: fixtext
        - name: component_id
          in: query
          required: false
          description: Filter to a specific component within the project.
          schema:
            type: integer
        - name: author_id
          in: query
          required: false
          description: Filter by comment author.
          schema:
            type: integer
        - name: q
          in: query
          required: false
          description: Text search within comment content.
          schema:
            type: string
        - name: resolved
          in: query
          required: false
          description: Filter by resolved state (true/false/all).
          schema:
            type: string
            enum:
              - 'true'
              - 'false'
              - all
          example: 'false'
      responses:
        '200':
          description: Paginated project comment rows with status counts
          content:
            application/json:
              schema:
                type: object
                required:
                  - rows
                  - pagination
                  - status_counts
                properties:
                  rows:
                    type: array
                    description: Comment rows with component context.
                    items:
                      type: object
                      properties:
                        id:
                          type: integer
                        rule_id:
                          type:
                            - integer
                            - 'null'
                        rule_displayed_name:
                          type:
                            - string
                            - 'null'
                        commentable_type:
                          type: string
                        component_id:
                          type:
                            - integer
                            - 'null'
                        component_name:
                          type:
                            - string
                            - 'null'
                        section:
                          type:
                            - string
                            - 'null'
                        author_name:
                          type:
                            - string
                            - 'null'
                        comment:
                          type: string
                        created_at:
                          type: string
                        triage_status:
                          type:
                            - string
                            - 'null'
                        triage_set_at:
                          type:
                            - string
                            - 'null'
                        adjudicated_at:
                          type:
                            - string
                            - 'null'
                        duplicate_of_review_id:
                          type:
                            - integer
                            - 'null'
                        triager_display_name:
                          type:
                            - string
                            - 'null'
                        triager_imported:
                          type: boolean
                        adjudicator_display_name:
                          type:
                            - string
                            - 'null'
                        adjudicator_imported:
                          type: boolean
                        responses_count:
                          type: integer
                        reactions:
                          type: object
                          properties:
                            up:
                              type: integer
                            down:
                              type: integer
                            mine:
                              type:
                                - string
                                - 'null'
                  pagination:
                    type: object
                    required:
                      - page
                      - per_page
                      - total
                    properties:
                      page:
                        type: integer
                      per_page:
                        type: integer
                      total:
                        type: integer
                  status_counts:
                    type: object
                    additionalProperties:
                      type: integer
              examples:
                comments:
                  summary: Project-wide comment queue
                  value:
                    rows:
                      - id: 26
                        rule_displayed_name: PHOS-03-000038
                        component_id: 1
                        component_name: Photon OS 3
                        triage_status: pending
                        comment: This requirement needs clarification.
                        responses_count: 0
                        reactions:
                          up: 0
                          down: 0
                          mine: null
                    pagination:
                      page: 1
                      per_page: 25
                      total: 3
                    status_counts:
                      pending: 1
                      concur: 1
                      informational: 1
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /projects/{projectId}/export/{type}:
    parameters:
      - $ref: '#/components/parameters/ProjectId'
      - name: type
        in: path
        required: true
        description: Export format.
        schema:
          type: string
          enum:
            - csv
            - xccdf
            - inspec
            - json_archive
        example: xccdf
    get:
      operationId: exportProject
      tags:
        - Projects
      summary: Export project data in the specified format
      description: Exports selected components from the project as CSV, XCCDF XML, InSpec profile, or JSON archive. Supports mode selection (working copy, vendor submission, published STIG) and optional SRG/membership inclusion. Returns a binary file download. Requires project membership.
      parameters:
        - name: component_ids
          in: query
          required: true
          description: Comma-separated IDs of components to include in the export.
          schema:
            type: string
          example: 29,30
        - name: mode
          in: query
          description: Export mode controlling which fields and rules are included.
          schema:
            type: string
            enum:
              - working_copy
              - vendor_submission
              - published_stig
          example: working_copy
        - name: include_srg
          in: query
          description: Include the source SRG in the export package.
          schema:
            type: string
            enum:
              - 'true'
        - name: include_memberships
          in: query
          description: Include project membership data in the export.
          schema:
            type: string
            enum:
              - 'true'
              - 'false'
        - name: exclude_satisfied_by
          in: query
          description: Exclude rules that are satisfied by another rule.
          schema:
            type: string
            enum:
              - 'true'
      responses:
        '200':
          description: Binary file download (CSV, XML, ZIP, or JSON)
          content:
            text/csv:
              schema:
                type: string
                format: binary
            application/xml:
              schema:
                type: string
                format: binary
            application/zip:
              schema:
                type: string
                format: binary
            application/json:
              schema:
                type: string
                format: binary
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /projects/create_from_backup:
    post:
      operationId: createFromBackup
      tags:
        - Projects
      summary: Create a new project from a JSON archive backup
      description: Creates a new project by restoring from a JSON archive (.zip). The archive must have been created by the json_archive export. Optionally override the project name. The archived project metadata (project.json) is restored onto the new project. The creating user becomes the project admin. Requires authentication.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - file
              properties:
                file:
                  type: string
                  format: binary
                  description: JSON archive .zip file from a previous export.
                name:
                  type: string
                  description: Override project name. Uses the archived name if omitted.
                  example: Restored Container Platform
      responses:
        '200':
          description: Dry-run returns preview (summary + warnings + project_defaults). Real create returns redirect URL + summary + toast.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/CreateFromBackupDryRunResponse'
                  - $ref: '#/components/schemas/CreateFromBackupResponse'
              examples:
                dry_run:
                  summary: Preview of what would be imported
                  value:
                    summary:
                      dry_run: true
                      components_imported: 2
                      rules_imported: 264
                      component_details:
                        - name: Photon OS 3
                          rule_count: 132
                    warnings: []
                    project_defaults:
                      name: My Original Project
                      description: A test project
                      visibility: discoverable
                created:
                  summary: Project created from archive
                  value:
                    redirect_url: /projects/123
                    summary:
                      components_imported: 2
                      rules_imported: 264
                    toast:
                      title: Project imported.
                      message:
                        - Project created from backup successfully.
                      variant: success
        '422':
          description: Import or preview failed
          content:
            application/json:
              schema:
                type: object
                required:
                  - toast
                properties:
                  toast:
                    $ref: '#/components/schemas/ToastObject'
                  warnings:
                    type: array
                    items:
                      type: string
                  project_defaults:
                    type: object
                    properties:
                      name:
                        type:
                          - string
                          - 'null'
                      description:
                        type:
                          - string
                          - 'null'
                      visibility:
                        type:
                          - string
                          - 'null'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /projects/{projectId}/import_backup:
    parameters:
      - $ref: '#/components/parameters/ProjectId'
    post:
      operationId: importBackup
      tags:
        - Projects
      summary: Import a JSON archive backup
      description: Imports components, rules, reviews, and memberships from a JSON archive (.zip) into the project, replacing or augmenting existing content. Returns 200 with a toast + summary. Requires admin role on the project. The archive must have been created by the json_archive export format.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - file
              properties:
                file:
                  type: string
                  format: binary
                  description: JSON archive .zip file from a previous export.
      responses:
        '200':
          description: Backup imported (default mode) or dry-run preview completed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ImportBackupResponse'
              examples:
                imported:
                  summary: Archive imported successfully
                  value:
                    toast:
                      title: Backup restored.
                      message:
                        - Backup restored successfully.
                      variant: success
                    summary:
                      components_imported: 2
                      rules_imported: 264
                      satisfactions_imported: 12
                      reviews_imported: 48
                      memberships_imported: 3
                      srgs_imported: 1
                      component_details:
                        - name: Photon OS 3
                          rule_count: 132
                          srg_title: General Purpose Operating System SRG
                          srg_version: V3R3
                    warnings: []
        '422':
          description: Import failed (validation errors)
          content:
            application/json:
              schema:
                type: object
                required:
                  - toast
                properties:
                  toast:
                    $ref: '#/components/schemas/ToastObject'
                  warnings:
                    type: array
                    items:
                      type: string
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /projects/{projectId}/histories:
    parameters:
      - $ref: '#/components/parameters/ProjectId'
    get:
      operationId: getProjectHistories
      tags:
        - Projects
      summary: Audit history for the project
      description: Returns the 50 most recent audit trail entries for the project, including component and rule changes. Used by the project history sidebar. Requires project membership.
      responses:
        '200':
          description: Recent audit entries
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AuditEntry'
              examples:
                history:
                  summary: Recent project changes
                  value:
                    - id: 500
                      auditable_type: Component
                      auditable_id: 38
                      action: update
                      name: Demo Admin
                      created_at: '2026-05-28T15:00:00Z'
                      audited_changes:
                        - field: released
                          prev_value: false
                          new_value: true
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /components:
    get:
      operationId: listComponents
      tags:
        - Components
      summary: List released components
      description: Returns all released (published) components visible to the current user. Released components are read-only snapshots that have been through the DISA review process. Requires authentication.
      responses:
        '200':
          description: Released components
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ComponentIndexResponse'
              examples:
                components:
                  summary: Two released components
                  value:
                    - id: 29
                      name: Container SRG
                      prefix: CNTR
                      version: 1
                      release: 1
                      released: true
                    - id: 30
                      name: Photon OS 3
                      prefix: PHOS-03
                      version: 1
                      release: 1
                      released: true
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /projects/{projectId}/components:
    parameters:
      - $ref: '#/components/parameters/ProjectId'
    post:
      operationId: createComponent
      tags:
        - Components
      summary: Create or duplicate a component in a project
      description: Creates a new component in the project from one or more declared source SRGs, duplicates an existing component, or imports from an uploaded XCCDF/CSV file. Requires admin role on the project. The component is initialized with requirements from every declared source (full union by default, or the requirement_selections subset) — security_requirements_guide_id designates the primary.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                component:
                  $ref: '#/components/schemas/ComponentInput'
            examples:
              from_srg:
                summary: Create from an SRG baseline
                value:
                  component:
                    name: Container SRG
                    prefix: CNTR
                    title: Container Platform Security Technical Implementation Guide
                    version: 1
                    release: 1
                    security_requirements_guide_id: 1
              multi_source_srg:
                summary: Create a dual-home SRG component from two core sources
                value:
                  component:
                    name: Container Platform SRG
                    prefix: CNTR
                    title: Container Platform Security Requirements Guide
                    version: 1
                    release: 1
                    document_type: srg
                    security_requirements_guide_id: 1
                    declared_source_srg_ids:
                      - 1
                      - 2
              one_call_with_extras:
                summary: Create complete in one call — metadata, questions, advanced fields
                value:
                  component:
                    name: Container SRG
                    prefix: CNTR
                    title: Container Platform Security Technical Implementation Guide
                    version: 1
                    release: 1
                    security_requirements_guide_id: 1
                    advanced_fields: true
                    component_metadata_attributes:
                      data:
                        Vendor: Acme
                        POC: Sam
                    additional_questions_attributes:
                      - name: Deployment environment
                        question_type: dropdown
                        options:
                          - Cloud
                          - On-prem
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                  description: XCCDF XML or CSV file to import as a component.
      responses:
        '200':
          description: Component created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                created:
                  summary: Component created from SRG
                  value:
                    toast:
                      title: Component created.
                      message:
                        - Successfully created component Container SRG.
                      variant: success
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /components/{componentId}:
    parameters:
      - $ref: '#/components/parameters/ComponentId'
    get:
      operationId: getComponent
      tags:
        - Components
      summary: Component detail with rules
      description: 'Returns full component details including all rules with their content fields, for either document kind. Members (any role) receive the editor view; a non-member receives the read-only show view for a RELEASED component (released components are never concealed, whatever the project''s visibility). An unreleased component answers a non-member per the disclosure policy: 403 with the project admins to ask when the project is discoverable, or the concealment 404 when it is hidden.'
      responses:
        '200':
          description: Component with rules — the editor view for members, the read-only show view otherwise. A null effective_permissions marks the show branch. The two shapes share their core fields, so they are documented as anyOf.
          content:
            application/json:
              schema:
                anyOf:
                  - $ref: '#/components/schemas/ComponentEditorResponse'
                  - $ref: '#/components/schemas/ComponentShowResponse'
              examples:
                component:
                  summary: Component editor view (project member)
                  value:
                    id: 1
                    name: Photon OS 3
                    prefix: PHOS-03
                    title: Photon OS 3 STIG Readiness Guide
                    rules_count: 203
                    comment_phase: open
                    version: 1
                    release: 1
                    released: false
                non_member_show:
                  summary: Read-only show view (non-member, released component)
                  value:
                    id: 1
                    name: Photon OS 3
                    prefix: PHOS-03
                    title: Photon OS 3 STIG Readiness Guide
                    released: true
                    effective_permissions: null
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    put:
      operationId: updateComponent
      tags:
        - Components
      summary: Update component attributes
      description: Updates component metadata (name, prefix, version, release, description). Requires admin role on the parent project. Does not modify rules — use the rule endpoints for rule content changes.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                component:
                  $ref: '#/components/schemas/ComponentUpdateInput'
            examples:
              update:
                summary: Update version and release
                value:
                  component:
                    version: 2
                    release: 1
      responses:
        '200':
          description: Component updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                updated:
                  summary: Component updated
                  value:
                    toast:
                      title: Component updated.
                      message:
                        - Successfully updated component.
                      variant: success
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    patch:
      operationId: patchComponent
      tags:
        - Components
      summary: Partial update of component attributes
      description: Partial update — same behavior as PUT but only supplied fields are changed. Requires admin role on the parent project.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                component:
                  $ref: '#/components/schemas/ComponentUpdateInput'
      responses:
        '200':
          description: Component updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    delete:
      operationId: deleteComponent
      tags:
        - Components
      summary: Delete a component and all its rules
      description: Permanently deletes the component, all its rules, reviews, and associated data. Requires admin role on the parent project. This action cannot be undone.
      responses:
        '200':
          description: Component deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                deleted:
                  summary: Component deleted
                  value:
                    toast:
                      title: Component deleted.
                      message:
                        - Successfully deleted component.
                      variant: success
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /components/{componentId}/comments:
    parameters:
      - $ref: '#/components/parameters/ComponentId'
    get:
      operationId: getComponentComments
      tags:
        - Components
      summary: Paginated triage table for component comments
      description: Returns paginated public comments on this component's rules, with triage status counts and filtering. Powers the triage page table and split-pane views. Supports filtering by status, section, rule, author, text search, and resolution state. Requires project membership.
      parameters:
        - $ref: '#/components/parameters/TriageStatusFilter'
        - $ref: '#/components/parameters/PageParam'
        - $ref: '#/components/parameters/PerPageParam'
        - name: section
          in: query
          description: Filter by requirement section (fixtext, check_content, etc.).
          schema:
            type: string
          example: fixtext
        - name: rule_id
          in: query
          description: Filter to comments on a specific rule.
          schema:
            type: integer
          example: 100
        - name: author_id
          in: query
          description: Filter to comments by a specific author.
          schema:
            type: integer
          example: 42
        - name: q
          in: query
          description: Full-text search across comment content.
          schema:
            type: string
          example: container image
        - name: resolved
          in: query
          required: false
          description: Filter by resolved state (true/false/all).
          schema:
            type: string
            enum:
              - 'true'
              - 'false'
              - all
          example: 'false'
        - name: commentable_type
          in: query
          required: false
          description: Restrict rows to comments on requirements ("rule") or on the component itself ("component"). Absent or any other value returns both.
          schema:
            type: string
            enum:
              - rule
              - component
          example: rule
        - name: include_rule_content
          in: query
          description: Include rule content fields for split-pane triage view.
          schema:
            type: string
            enum:
              - 'true'
      responses:
        '200':
          description: Paginated comment rows with status counts
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaginatedComments'
              examples:
                comments:
                  summary: Pending comments on Container SRG
                  value:
                    rows:
                      - id: 44
                        commentable_type: BaseRule
                        rule_displayed_name: CNTR-00-000050
                        section: fixtext
                        author_name: John Osborne
                        author_email: josborne@example.org
                        comment: This requirement needs clarification...
                        triage_status: pending
                        created_at: '2026-05-19T16:15:00Z'
                        responses_count: 0
                        reactions:
                          up: 1
                          down: 0
                    pagination:
                      page: 1
                      per_page: 25
                      total: 1
                      total_comments: 10
                    status_counts:
                      pending: 10
                      concur: 2
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /components/{componentId}/export/{type}:
    parameters:
      - $ref: '#/components/parameters/ComponentId'
      - name: type
        in: path
        required: true
        description: Export format.
        schema:
          type: string
          enum:
            - csv
            - xccdf
            - inspec
            - json_archive
            - disposition_csv
        example: xccdf
    get:
      operationId: exportComponent
      tags:
        - Components
      summary: Export component in the specified format
      description: 'Downloads the component as CSV, XCCDF XML, InSpec profile, JSON archive, or disposition CSV. Supports mode selection (working_copy, vendor_submission, published_stig) via query param. The xccdf type is kind-routed by the component''s document type: SRG components export their authored requirements through the published_srg mode (only Applicable requirements publish); STIG components use published_stig. disposition_csv is for DISA comment triage matrix export. Requires project membership.'
      parameters:
        - name: mode
          in: query
          description: Export mode controlling which fields are included. The xccdf type ignores this parameter — its mode is derived from the component's document type (published_srg for SRG kind, published_stig for STIG).
          schema:
            type: string
            enum:
              - working_copy
              - vendor_submission
              - published_stig
          example: working_copy
        - name: triage_status
          in: query
          description: Filter disposition CSV by triage status.
          schema:
            type: string
          example: pending
      responses:
        '200':
          description: Binary file download
          content:
            text/csv:
              schema:
                type: string
                format: binary
            application/xml:
              schema:
                type: string
                format: binary
            application/zip:
              schema:
                type: string
                format: binary
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /components/{componentId}/lock:
    parameters:
      - $ref: '#/components/parameters/ComponentId'
    post:
      operationId: lockComponent
      tags:
        - Components
      summary: Lock all unlocked rules in a component
      description: Locks every currently-unlocked rule in the component, preventing further edits. Requires component-admin authority. Already-locked rules are unaffected.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - review
              properties:
                review:
                  description: The bulk-lock surface is action + comment only — one review row is generated per locked requirement carrying both, and the comment is required (each generated review validates comment presence). Other review keys (section, responding_to_review_id, component_id) are ignored.
                  type: object
                  required:
                    - action
                    - comment
                  properties:
                    action:
                      type: string
                      description: The review action recorded on every generated row.
                      enum:
                        - lock_control
                      example: lock_control
                    comment:
                      type: string
                      description: Audit trail comment explaining why the component was locked.
                      example: Locking for DISA submission review.
            examples:
              lock:
                summary: Lock with audit comment
                value:
                  review:
                    action: lock_control
                    comment: Locking for DISA submission review.
      responses:
        '200':
          description: Lockable rules locked. Rules with incomplete data are SKIPPED, not failed — Not Yet Determined without satisfactions, Does Not Meet without mitigations, Inherently Meets without an artifact description — and each skip is named in a warning line appended to the message (variant becomes warning).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                locked:
                  summary: All candidates locked
                  value:
                    toast:
                      title: Locked 2 controls.
                      message:
                        - 'Locked: CNTR-00-000001, CNTR-00-000002'
                      variant: success
                locked_with_skips:
                  summary: Some rules skipped for incomplete data
                  value:
                    toast:
                      title: Locked 1 control.
                      message:
                        - |-
                          Locked: CNTR-00-000001

                          Not Yet Determined (skipped): CNTR-00-000002
                      variant: warning
        '422':
          description: Every candidate rule was skipped — nothing could be locked.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                nothing_lockable:
                  summary: All candidates skipped
                  value:
                    toast:
                      title: No controls could be locked.
                      message:
                        - 'Not Yet Determined (skipped): CNTR-00-000001, CNTR-00-000002'
                      variant: warning
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /components/{componentId}/lock_sections:
    parameters:
      - $ref: '#/components/parameters/ComponentId'
    patch:
      operationId: lockSections
      tags:
        - Components
      summary: Lock or unlock sections on every unlocked rule
      description: Locks (or unlocks) the named content sections across all unlocked requirement rows in the component — both document kinds. Section names come from the lockable-section vocabulary (Title, Severity, Status, Fix, Check, ...). Locked sections cannot be edited until unlocked. Requires reviewer role or higher on the component. Returns 422 when any section name is not in the vocabulary.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - sections
                - locked
              properties:
                sections:
                  type: array
                  description: Section names to lock or unlock.
                  items:
                    type: string
                    enum:
                      - Title
                      - Severity
                      - Status
                      - Fix
                      - Check
                      - Vulnerability Discussion
                      - DISA Metadata
                      - Vendor Comments
                      - Artifact Description
                      - XCCDF Metadata
                  example:
                    - Fix
                    - Check
                locked:
                  type: boolean
                  description: True locks the named sections; false unlocks them.
                  example: true
                comment:
                  type:
                    - string
                    - 'null'
                  description: Optional audit comment recorded on each changed rule. When blank, a generated comment naming the action and sections is recorded instead.
                  example: Content freeze for the review window.
            examples:
              lock_fix_check:
                summary: Lock the Fix and Check sections
                value:
                  sections:
                    - Fix
                    - Check
                  locked: true
                  comment: Content freeze for the review window.
      responses:
        '200':
          description: Section lock applied across all unlocked rules
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                locked:
                  summary: Sections locked
                  value:
                    toast:
                      title: Section lock applied
                      message:
                        - Locked 2 section(s) on 264 rule(s)
                      variant: success
        '422':
          description: A section name is not in the lockable vocabulary
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                invalid_section:
                  summary: Unknown section name
                  value:
                    toast:
                      title: Invalid sections
                      message:
                        - 'Not recognized: fixtext'
                      variant: danger
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /components/{componentId}/histories:
    parameters:
      - $ref: '#/components/parameters/ComponentId'
    get:
      operationId: getComponentHistories
      tags:
        - Components
      summary: Audit history for the component
      description: Returns the 50 most recent audit trail entries for the component, including rule changes, review actions, and metadata updates. Used by the component history sidebar. Requires project membership.
      responses:
        '200':
          description: Recent audit entries
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AuditEntry'
              examples:
                history:
                  summary: Recent component changes
                  value:
                    - id: 600
                      auditable_type: Rule
                      auditable_id: 812
                      action: update
                      name: Demo Admin
                      created_at: '2026-05-28T15:00:00Z'
                      audited_changes:
                        - field: status
                          prev_value: Not Yet Determined
                          new_value: Applicable - Configurable
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /components/{componentId}/rules:
    parameters:
      - $ref: '#/components/parameters/ComponentId'
    get:
      operationId: listComponentRules
      tags:
        - Rules
      summary: List all rules for a component
      description: Returns all rules in the component with full content fields (title, fixtext, check_content, vuln_discussion, etc.). Used by the component editor to populate the rule list. Requires project membership.
      responses:
        '200':
          description: All component rules
          content:
            application/json:
              schema:
                type: array
                description: 'Items are shaped by the component''s document_type: STIG rules or authored SRG requirements.'
                items:
                  oneOf:
                    - $ref: '#/components/schemas/RuleEditorResponse'
                    - $ref: '#/components/schemas/AuthoredSrgRuleEditorResponse'
              examples:
                rules:
                  summary: Two rules
                  value:
                    - id: 100
                      rule_id: CNTR-00-000050
                      title: Container images must be signed
                      status: Applicable - Configurable
                      locked: false
                      satisfies: []
                      satisfied_by: []
                    - id: 101
                      rule_id: CNTR-00-000051
                      title: Container images must come from approved registries
                      status: Applicable - Configurable
                      locked: false
                      satisfies: []
                      satisfied_by: []
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    post:
      operationId: createRule
      tags:
        - Rules
      summary: Create a new requirement in a component
      description: 'Creates a requirement in one call, shaped by the component''s document_type — a STIG rule (seeded from the source SRG''s CCI-000366 baseline row; severity and weight are inherited from it) or an authored SRG requirement. Content fields apply at creation: provided values win over seeded defaults, and provided nested attributes replace the built defaults. Duplicate mode copies an existing requirement of THIS component. Requirement numbers are server-owned and assigned from the component''s sequence. Blank/content creation requires project admin; duplication requires author. Returns 422 when a STIG component''s source SRG has no CCI-000366 baseline row, or when provided content fails the kind''s validations.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                rule:
                  description: Creation mode plus the one-call content surface (the same fields the update endpoint accepts; nested attributes carry no id at creation).
                  allOf:
                    - type: object
                      properties:
                        duplicate:
                          type: boolean
                          description: Duplicate an existing requirement instead of creating a blank one.
                          example: false
                        id:
                          type: integer
                          description: Source requirement id to duplicate (required when duplicate is true; must belong to this component).
                          example: 100
                    - $ref: '#/components/schemas/RuleInput'
            examples:
              create:
                summary: Create a blank requirement
                value:
                  rule:
                    duplicate: false
              one_call_content:
                summary: Create a STIG rule with content in one call
                value:
                  rule:
                    duplicate: false
                    title: The container platform must enforce approved authorizations.
                    status: Applicable - Configurable
                    vendor_comments: Met by the recommended runtime configuration.
              authored_srg:
                summary: Create an authored SRG requirement with content
                value:
                  rule:
                    duplicate: false
                    title: The application must enforce approved authorizations for logical access.
                    status: Applicable
                    fixtext: Configure the application to enforce approved authorizations.
              duplicate:
                summary: Duplicate an existing requirement
                value:
                  rule:
                    duplicate: true
                    id: 100
      responses:
        '200':
          description: Rule created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RuleCreateResponse'
              examples:
                created:
                  summary: Control created
                  value:
                    toast:
                      title: Control created.
                      message:
                        - Successfully created control.
                      variant: success
                    data:
                      id: 5000
                      rule_id: '000204'
                      title: New container security requirement
                      status: Not Yet Determined
                      locked: false
                      satisfies: []
                      satisfied_by: []
          links:
            GetCreatedRule:
              operationId: getRule
              parameters:
                ruleId: $response.body#/data/id
              description: Fetch the newly created rule.
            UpdateCreatedRule:
              operationId: updateRule
              parameters:
                ruleId: $response.body#/data/id
              description: Update the newly created rule.
            DeleteCreatedRule:
              operationId: deleteRule
              parameters:
                ruleId: $response.body#/data/id
              description: Delete the newly created rule.
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /components/{componentId}/rules_picker:
    parameters:
      - $ref: '#/components/parameters/ComponentId'
    get:
      operationId: getRulesPicker
      tags:
        - Rules
      summary: Lightweight rule list for picker UI
      description: 'Returns a compact list of requirements for dropdown/picker selection (e.g., the move-to-rule admin action or duplicate-of picker). Lighter than the full rules list — omits content fields. Rows are shaped by the component''s document_type: STIG rule objects carry the satisfaction relationships, authored SRG requirement objects omit them entirely. Requires project membership.'
      responses:
        '200':
          description: Requirements for picker selection
          content:
            application/json:
              schema:
                type: object
                required:
                  - rules
                properties:
                  rules:
                    type: array
                    description: All requirements in picker shape, one variant per document kind — the shapes are disjoint and never collapse into one list.
                    items:
                      oneOf:
                        - $ref: '#/components/schemas/RulePickerResponse'
                        - $ref: '#/components/schemas/AuthoredSrgRulePickerResponse'
              examples:
                stig_picker:
                  summary: STIG rules for dropdown
                  value:
                    rules:
                      - id: 100
                        rule_id: '000050'
                        displayed_name: CNTR-00-000050
                        title: Container images must be signed
                        locked: false
                        satisfies: []
                        satisfied_by: []
                      - id: 101
                        rule_id: '000051'
                        displayed_name: CNTR-00-000051
                        title: Container images must come from approved registries
                        locked: true
                        satisfies: []
                        satisfied_by: []
                srg_picker:
                  summary: Authored SRG requirements for dropdown
                  value:
                    rules:
                      - id: 10633
                        rule_id: '000001'
                        displayed_name: RCPK-00-000001
                        title: The container platform must enforce approved authorizations
                        status: Applicable
                        locked: false
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /components/detect_srg:
    post:
      operationId: detectSrg
      tags:
        - Components
      summary: Detect which SRG a spreadsheet belongs to
      description: Analyzes an uploaded spreadsheet (CSV/XLSX) to determine which SRG its rule IDs match. Used by the component creation flow to auto-select the correct SRG when importing from a spreadsheet. Returns the matched SRG's id, srg_id, title, and version. Returns 422 if no file provided, no SRG IDs found, no matching SRG exists, or IDs map to multiple SRGs.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - file
              properties:
                file:
                  type: string
                  format: binary
                  description: CSV or XLSX spreadsheet containing rule IDs.
      responses:
        '200':
          description: Matched SRG
          content:
            application/json:
              schema:
                type: object
                required:
                  - id
                  - srg_id
                  - title
                  - version
                additionalProperties: false
                properties:
                  id:
                    type: integer
                    description: Database ID of the matched SRG.
                    example: 1
                  srg_id:
                    type: string
                    description: DISA SRG identifier string.
                    example: Container_Platform_SRG
                  title:
                    type: string
                    description: Full title of the matched SRG.
                    example: Container Platform Security Requirements Guide
                  version:
                    type: string
                    description: Version string (e.g. V2R4).
                    example: V2R4
              examples:
                detected:
                  summary: SRG detected from spreadsheet
                  value:
                    id: 1
                    srg_id: Container_Platform_SRG
                    title: Container Platform Security Requirements Guide
                    version: V2R4
        '422':
          description: No file, no SRG IDs found, no match, or ambiguous match
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: No SRG IDs found in spreadsheet
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /components/history:
    get:
      operationId: getComponentHistory
      tags:
        - Components
      summary: Revision history for a named component across versions
      description: Traces the version history of a component by name within a project. Returns an ordered array alternating between milestone entries (marking a version point) and diff entries (showing rule-level changes between consecutive releases). Used by the DiffViewer feature. Requires project membership. Component objects use ComponentBlueprint default view (9 fields).
      parameters:
        - name: project_id
          in: query
          required: true
          description: ID of the project containing the component versions.
          schema:
            type: integer
          example: 1
        - name: name
          in: query
          required: true
          description: Component name to trace across versions.
          schema:
            type: string
          example: Photon OS 3
      responses:
        '200':
          description: 'Ordered revision history. The array alternates between two entry types: 1. Milestone entries: { component: ComponentSummary } — marks a version point 2. Diff entries: { base_component: ComponentSummary, diff_component: ComponentSummary, changes: { rule_id: HistoryChangeEntry } }'
          content:
            application/json:
              schema:
                type: array
                description: Mixed array of milestone and diff entries. Milestones have a 'component' key. Diff entries have 'base_component', 'diff_component', and 'changes' keys.
                items:
                  type: object
                  properties:
                    component:
                      description: Milestone entry — marks this version in the timeline.
                      $ref: '#/components/schemas/ComponentSummary'
                    base_component:
                      description: Previous version in a diff pair.
                      $ref: '#/components/schemas/ComponentSummary'
                    diff_component:
                      description: Current version in a diff pair.
                      $ref: '#/components/schemas/ComponentSummary'
                    changes:
                      type: object
                      description: Rule-level changes keyed by rule_id. Each value describes what changed between the base and diff versions.
                      additionalProperties:
                        $ref: '#/components/schemas/HistoryChangeEntry'
              examples:
                two_versions:
                  summary: Two versions of Photon OS 3 with milestone + diff
                  value:
                    - component:
                        id: 1
                        name: Photon OS 3
                        prefix: PHOS-03
                        version: 1
                        release: 1
                    - base_component:
                        id: 1
                        name: Photon OS 3
                        prefix: PHOS-03
                        version: 1
                        release: 1
                      diff_component:
                        id: 2
                        name: Photon OS 3
                        prefix: PHOS-03
                        version: 1
                        release: 2
                      changes:
                        '000050':
                          change: updated
                          base:
                            rule_id: '000050'
                            title: Original title
                            fix: Original fix text
                          diff:
                            rule_id: '000050'
                            title: Updated title
                            fix: Updated fix text
                    - component:
                        id: 2
                        name: Photon OS 3
                        prefix: PHOS-03
                        version: 1
                        release: 2
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /components/{componentId}/related:
    parameters:
      - $ref: '#/components/parameters/ComponentId'
    get:
      operationId: searchRelatedComponents
      tags:
        - Components
      summary: Find components sharing the same SRG baseline
      description: Returns other components that are based on the same SRG as this component. Used by the DiffViewer to find peer components for side-by-side comparison. Scoped to components the current user can access plus released components. Returns a hand-built hash (NOT ComponentBlueprint).
      responses:
        '200':
          description: Related components sharing the same SRG
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: integer
                      example: 4
                    name:
                      type: string
                      example: Photon OS 3
                    version:
                      type:
                        - integer
                        - 'null'
                      example: 1
                    prefix:
                      type: string
                      example: PHOS-03
                    release:
                      type:
                        - integer
                        - 'null'
                      example: 1
                    project_id:
                      type: integer
                      example: 3
                    project_name:
                      type: string
                      example: vSphere 7.0
              examples:
                related:
                  summary: Two peer components from different projects
                  value:
                    - id: 4
                      name: Photon OS 3
                      version: 1
                      prefix: PHOS-03
                      release: 1
                      project_id: 3
                      project_name: vSphere 7.0
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /components/{componentId}/find:
    parameters:
      - $ref: '#/components/parameters/ComponentId'
    post:
      operationId: findRulesInComponent
      tags:
        - Components
      summary: Search requirements within a component by text
      description: Full-text search across requirement titles, fix text, vendor comments, status justification, artifact description, vulnerability discussion, mitigations, and check content within a single component. The same field list applies to both document kinds; results are shaped by the component's document_type. Returns matching requirements in rule_id order. Used by the in-component search feature.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - find
              properties:
                find:
                  type: string
                  description: Case-insensitive search term matched against all rule text fields.
                  example: container image
            examples:
              search:
                summary: Search for container image references
                value:
                  find: container image
      responses:
        '200':
          description: 'Matching requirements in rule_id order, shaped by the component''s document_type: STIG rule objects for stig components, authored SRG requirement objects for srg components.'
          content:
            application/json:
              schema:
                type: array
                items:
                  oneOf:
                    - $ref: '#/components/schemas/RuleEditorResponse'
                    - $ref: '#/components/schemas/AuthoredSrgRuleEditorResponse'
              examples:
                results:
                  summary: Two matching rules
                  value:
                    - id: 100
                      rule_id: CNTR-00-000050
                      title: Container images must be signed
                      status: Applicable - Configurable
                      locked: false
                      satisfies: []
                      satisfied_by: []
                    - id: 101
                      rule_id: CNTR-00-000051
                      title: Container images must come from approved registries
                      status: Applicable - Configurable
                      locked: false
                      satisfies: []
                      satisfied_by: []
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /components/{componentId}/preview_spreadsheet_update:
    parameters:
      - $ref: '#/components/parameters/ComponentId'
    post:
      operationId: previewSpreadsheetUpdate
      tags:
        - Components
      summary: Preview changes from a spreadsheet import
      description: 'Parses an uploaded spreadsheet (CSV/XLSX) and returns a diff of what would change if applied, without modifying any data. Returns four arrays: updated (rules with changes), unchanged (no diff), skipped_locked (locked rules or inherited rules), and warnings (SRG IDs not found). Requires author role on the component.'
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - file
              properties:
                file:
                  type: string
                  format: binary
                  description: CSV or XLSX spreadsheet file containing rule updates.
      responses:
        '200':
          description: Preview of changes grouped by outcome
          content:
            application/json:
              schema:
                type: object
                required:
                  - updated
                  - unchanged
                  - skipped_locked
                  - warnings
                properties:
                  updated:
                    type: array
                    description: Rules with detected changes.
                    items:
                      type: object
                      properties:
                        rule_id:
                          type: string
                          example: '000050'
                        srg_id:
                          type:
                            - string
                            - 'null'
                          example: SRG-APP-000014-CTR-000035
                        changes:
                          type: object
                          description: Changed fields with [old_value, new_value] pairs.
                          additionalProperties:
                            type: array
                            items: {}
                  unchanged:
                    type: array
                    description: Rules with no differences from spreadsheet.
                    items:
                      type: object
                      properties:
                        rule_id:
                          type: string
                        srg_id:
                          type:
                            - string
                            - 'null'
                        reason:
                          type: string
                          example: no changes
                  skipped_locked:
                    type: array
                    description: Rules skipped because they are locked or inherited.
                    items:
                      type: object
                      properties:
                        rule_id:
                          type: string
                        srg_id:
                          type:
                            - string
                            - 'null'
                        reason:
                          type: string
                          enum:
                            - locked
                            - inherited
                            - section locked
                          example: locked
                        skipped_fields:
                          type: array
                          description: Fields that were locked (present only for section-locked skips).
                          items:
                            type: string
                  warnings:
                    type: array
                    description: SRG IDs from the spreadsheet that were not found in the component.
                    items:
                      type: string
                    example:
                      - SRG ID SRG-APP-999999 not found in component
              examples:
                preview:
                  summary: Mixed preview with updates, unchanged, and skipped rules
                  value:
                    updated:
                      - rule_id: '000050'
                        srg_id: SRG-APP-000014-CTR-000035
                        changes:
                          fixtext:
                            - Old fix text
                            - Updated fix text
                    unchanged:
                      - rule_id: '000051'
                        srg_id: SRG-APP-000023-CTR-000040
                        reason: no changes
                    skipped_locked:
                      - rule_id: '000001'
                        srg_id: SRG-APP-000001-CTR-000001
                        reason: locked
                    warnings: []
        '422':
          description: No file provided or parse error
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    description: Error message.
              examples:
                no_file:
                  summary: No file uploaded
                  value:
                    error: No file was provided. Please upload a CSV or XLSX file.
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /components/{componentId}/apply_spreadsheet_update:
    parameters:
      - $ref: '#/components/parameters/ComponentId'
    patch:
      operationId: applySpreadsheetUpdate
      tags:
        - Components
      summary: Apply changes from a spreadsheet import
      description: Applies rule updates from an uploaded spreadsheet (CSV/XLSX) to the component. Requires admin role on the component. Use the preview endpoint first to review changes before applying. Creates audit trail entries for each modified rule.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - file
              properties:
                file:
                  type: string
                  format: binary
                  description: CSV or XLSX spreadsheet file containing rule updates.
      responses:
        '200':
          description: Spreadsheet changes applied successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                success:
                  summary: Rules updated from spreadsheet
                  value:
                    toast:
                      title: Spreadsheet applied.
                      message:
                        - Successfully updated 12 rules from spreadsheet.
                      variant: success
        '422':
          description: No file provided or application error
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
              examples:
                no_file:
                  summary: No file uploaded
                  value:
                    error: No file was provided. Please upload a CSV or XLSX file.
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /components/{componentId}/release:
    parameters:
      - $ref: '#/components/parameters/ComponentId'
    post:
      operationId: releaseComponent
      tags:
        - Components
      summary: Release an SRG component to the catalog
      description: 'Releases an SRG-kind component in one transaction: verifies every live requirement is decided (never Not Yet Determined) and locked, mints the final published identifiers, generates the published SRG XCCDF, creates the catalog SecurityRequirementsGuide entry with its columns derived from that document, copies the published requirements onto the entry, and flags the component released. The released entry behaves exactly like an uploaded SRG — new components can base on it immediately. Requires the author role on the component. STIG readiness components do not use this endpoint (their release is the released flag on component update).'
      responses:
        '200':
          description: Component released and attached to the catalog
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ComponentReleaseResponse'
              examples:
                released:
                  summary: First release of a derived SRG
                  value:
                    toast:
                      title: Component released.
                      message:
                        - Container Best Practice SRG - Ver 1, Rel 1 is now in the SRG catalog.
                      variant: success
                    catalog_srg:
                      id: 42
                      srg_id: Container_Best_Practice_SRG
                      version: V1R1
                      name: Container Best Practice SRG - Ver 1, Rel 1
                    changelog:
                      version: V1R1
                      removals: []
                      text: |-
                        Container Best Practice SRG V1R1 — Release Changelog

                        No requirements were removed in this release.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          description: Release blocked — the component is not an SRG component, is already released, is missing its version/release pair or abbreviation, still has Not Yet Determined requirements, or has unlocked requirements. The toast messages carry every blocking reason.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                undecided:
                  summary: Undecided requirements block the release
                  value:
                    toast:
                      title: Could not release component.
                      message:
                        - 'release is blocked: 3 live requirement(s) still Not Yet Determined — every requirement must be decided'
                      variant: danger
  /components/bulk_export/{type}:
    parameters:
      - name: type
        in: path
        required: true
        description: Export format for the bulk download.
        schema:
          type: string
          enum:
            - csv
            - xccdf
            - inspec
        example: csv
    get:
      operationId: bulkExportComponents
      tags:
        - Components
      summary: Bulk export multiple released components
      description: Exports multiple released components as a single download in the specified format. Component IDs are passed as a comma-separated query parameter. Returns a binary file (zip for multiple components). Returns a JSON error toast if the export type is unsupported or no component IDs are provided.
      parameters:
        - name: component_ids
          in: query
          required: true
          description: Comma-separated list of released component IDs to export.
          schema:
            type: string
          example: 29,30,31
      responses:
        '200':
          description: Binary file download
          content:
            application/zip:
              schema:
                type: string
                format: binary
            application/xml:
              schema:
                type: string
                format: binary
        '400':
          description: Invalid export type or no components selected
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                bad_type:
                  summary: Unsupported export type
                  value:
                    toast:
                      title: Export error.
                      message:
                        - 'Unsupported export type: pdf'
                      variant: danger
                no_components:
                  summary: No components selected
                  value:
                    toast:
                      title: Export error.
                      message:
                        - No components selected for export.
                      variant: danger
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /api/components/compare:
    get:
      operationId: compareComponents
      tags:
        - Components
      summary: Side-by-side rule comparison between two peer components
      description: Compares rules between a base (older) and diff (newer) component sharing the same SRG. Returns a rule-by-rule diff keyed by field name, with metadata about both components. Used by the DiffViewer stepper in the component editor. Requires authentication and access to both components.
      parameters:
        - name: base_id
          in: query
          required: true
          description: ID of the base (older) component to compare from.
          schema:
            type: integer
          example: 29
        - name: diff_id
          in: query
          required: true
          description: ID of the diff (newer) component to compare against.
          schema:
            type: integer
          example: 30
      responses:
        '200':
          description: Rule-by-rule diff with metadata envelope
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    type: object
                    description: Diff keyed by rule_id. Each entry compares the inspec_control_file content between the base and diff components for that rule.
                    additionalProperties:
                      type: object
                      properties:
                        base:
                          type:
                            - string
                            - 'null'
                          description: InSpec control file content from the base component. Null if rule not present.
                        diff:
                          type:
                            - string
                            - 'null'
                          description: InSpec control file content from the diff component. Null if rule not present.
                        changed:
                          type: boolean
                          description: Whether the InSpec content differs between versions.
                  meta:
                    type: object
                    description: Metadata about the compared components.
                    properties:
                      base_id:
                        type: integer
                        example: 29
                      diff_id:
                        type: integer
                        example: 30
                      rules_count:
                        type: integer
                        example: 264
              examples:
                diff:
                  summary: Two rules compared by generated InSpec control
                  value:
                    data:
                      '000050':
                        base: |-
                          control 'CNTR-00-000050' do
                            title 'Container images must be signed'
                          end
                        diff: |-
                          control 'CNTR-00-000050' do
                            title 'Container images must be signed and verified'
                          end
                        changed: true
                      '000051':
                        base: |-
                          control 'CNTR-00-000051' do
                            title 'Approved registries only'
                          end
                        diff: |-
                          control 'CNTR-00-000051' do
                            title 'Approved registries only'
                          end
                        changed: false
                    meta:
                      base_id: 29
                      diff_id: 30
                      rules_count: 264
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /rules/{ruleId}:
    parameters:
      - $ref: '#/components/parameters/RuleId'
    get:
      operationId: getRule
      tags:
        - Rules
      summary: Rule detail
      description: Returns the full requirement editor payload for either document kind — checks, descriptions, and review history for both; satisfactions (satisfies/satisfied_by) appear on stig rules only, and authored SRG requirements omit those keys entirely.
      responses:
        '200':
          description: Rule data
          content:
            application/json:
              schema:
                description: 'Shaped by the parent component''s document_type: a STIG rule or an authored SRG requirement.'
                oneOf:
                  - $ref: '#/components/schemas/RuleEditorResponse'
                  - $ref: '#/components/schemas/AuthoredSrgRuleEditorResponse'
          links:
            UpdateThisRule:
              operationId: updateRule
              parameters:
                ruleId: $response.body#/id
            DeleteThisRule:
              operationId: deleteRule
              parameters:
                ruleId: $response.body#/id
            RevertThisRule:
              operationId: revertRule
              parameters:
                ruleId: $response.body#/id
            CreateReviewOnRule:
              operationId: createRuleReview
              parameters:
                ruleId: $response.body#/id
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    patch:
      operationId: patchRule
      tags:
        - Rules
      summary: Partial update of rule attributes
      description: Partial update — send only changed fields. Updates one or more rule fields. Requires author or admin role on the component.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                rule:
                  $ref: '#/components/schemas/RuleInput'
            examples:
              stig_update:
                summary: STIG rule status change
                value:
                  rule:
                    status: Applicable - Configurable
                    vendor_comments: Met by the recommended runtime configuration.
              authored_srg_update:
                summary: Authored SRG requirement decided Not Applicable
                value:
                  rule:
                    status: Not Applicable
                    status_justification: The platform provides no wireless interfaces to configure.
              nested_content:
                summary: Update check content and record an audit comment
                value:
                  rule:
                    audit_comment: Check procedure clarified per review feedback.
                    checks_attributes:
                      - id: 311
                        content: Review the container platform configuration to verify approved authorizations are enforced.
      responses:
        '200':
          description: Rule updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    put:
      operationId: updateRule
      tags:
        - Rules
      summary: Full replacement of rule attributes
      description: Full replacement — all fields required. Updates rule fields. Requires author or admin role on the component.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                rule:
                  $ref: '#/components/schemas/RuleInput'
            examples:
              stig_update:
                summary: STIG rule full update
                value:
                  rule:
                    status: Applicable - Configurable
                    title: The container platform must enforce approved authorizations.
                    fixtext: Configure the container platform to enforce approved authorizations.
                    vendor_comments: Met by the recommended runtime configuration.
              authored_srg_update:
                summary: Authored SRG requirement update
                value:
                  rule:
                    status: Applicable
                    title: The application must enforce approved authorizations for logical access.
                    fixtext: Configure the application to enforce approved authorizations.
      responses:
        '200':
          description: Rule updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    delete:
      operationId: deleteRule
      tags:
        - Rules
      summary: Soft-delete a rule
      description: Marks the rule as deleted. Requires admin role on the component.
      responses:
        '200':
          description: Rule deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /rules/{ruleId}/revert:
    parameters:
      - $ref: '#/components/parameters/RuleId'
    post:
      operationId: revertRule
      tags:
        - Rules
      summary: Revert requirement fields to a previous audit version
      description: Restores the named audited fields to their prior values from the specified history entry. Applies to both document kinds — stig rules and authored SRG requirements. Requires author or admin role.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                audit_id:
                  type: integer
                  description: The audit history entry to revert from.
                  example: 4211
                fields:
                  type: array
                  description: The audited field names to restore.
                  items:
                    type: string
                  example:
                    - title
                audit_comment:
                  type: string
                  description: Recorded on the audit trail with the revert.
                  example: Restore the original wording
      responses:
        '200':
          description: Rule reverted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /rules/{ruleId}/section_locks:
    parameters:
      - $ref: '#/components/parameters/RuleId'
    patch:
      operationId: updateSectionLocks
      tags:
        - Rules
      summary: Lock or unlock a single section on a rule
      description: Locks or unlocks a single section (e.g., Fix, Check) on a rule. Requires reviewer role or higher on the parent component. Creates an audit entry.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - section
                - locked
              properties:
                section:
                  type: string
                  description: Section name to lock or unlock, from the lockable-section vocabulary.
                  enum:
                    - Title
                    - Severity
                    - Status
                    - Fix
                    - Check
                    - Vulnerability Discussion
                    - DISA Metadata
                    - Vendor Comments
                    - Artifact Description
                    - XCCDF Metadata
                  example: Fix
                locked:
                  type: boolean
                  description: Whether to lock (true) or unlock (false).
                  example: true
                comment:
                  type: string
                  description: Optional audit comment.
                  example: Locking the Fix section for final review
      responses:
        '200':
          description: Section locks updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RuleSectionLockResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /rules/{ruleId}/bulk_section_locks:
    parameters:
      - $ref: '#/components/parameters/RuleId'
    patch:
      operationId: bulkSectionLocks
      tags:
        - Rules
      summary: Bulk update locked sections
      description: Locks or unlocks multiple sections at once on a rule. Requires reviewer role or higher on the parent component. Creates an audit trail entry.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - sections
                - locked
              properties:
                sections:
                  type: array
                  description: Section names to lock or unlock, from the lockable-section vocabulary.
                  items:
                    type: string
                    enum:
                      - Title
                      - Severity
                      - Status
                      - Fix
                      - Check
                      - Vulnerability Discussion
                      - DISA Metadata
                      - Vendor Comments
                      - Artifact Description
                      - XCCDF Metadata
                  example:
                    - Fix
                    - Check
                locked:
                  type: boolean
                  description: Whether to lock (true) or unlock (false) the sections.
                  example: true
                comment:
                  type: string
                  description: Optional audit comment explaining the lock change.
                  example: Locking the Fix and Check sections for review
      responses:
        '200':
          description: Section locks updated, returns updated rule + toast
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RuleSectionLockResponse'
        '422':
          description: Invalid section names
          content:
            application/json:
              schema:
                type: object
                required:
                  - error
                properties:
                  error:
                    type: string
                    description: Names every unrecognized section.
                    example: 'Invalid sections: NotASection'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /rules/{ruleId}/search/related_rules:
    parameters:
      - $ref: '#/components/parameters/RuleId'
    get:
      operationId: getRelatedRules
      tags:
        - Rules
      summary: Find rules sharing the same SRG requirement
      description: Returns rules from other components and published STIGs that implement the same SRG requirement (matched by version/srg_id). Results are scoped to components the current user can access unless the user is an admin. The rules array is a mix of RuleEditorResponse (component rules) and StigRuleSummary (published STIG rules). The parents array is a mix of ComponentBlueprint :related and StigBlueprint :index for grouping. Stig rules only — the search keys off the stig rule's SRG-version linkage, so requests for an authored SRG requirement return 404 (no related-rules surface exists for that kind).
      responses:
        '200':
          description: Related rules with parent containers
          content:
            application/json:
              schema:
                type: object
                required:
                  - rules
                  - parents
                properties:
                  rules:
                    type: array
                    description: Mixed array of component rules (RuleEditorResponse, 38 fields) and published STIG rules (StigRuleSummary, 16 fields).
                    items: {}
                  parents:
                    type: array
                    description: 'Mixed array of parent containers: ComponentBlueprint :related (with nested project) and StigBlueprint :index (with severity_counts).'
                    items: {}
              examples:
                related:
                  summary: Rules from another component and a published STIG
                  value:
                    rules:
                      - id: 200
                        rule_id: SV-222387r960735_rule
                        title: The application must limit logon sessions
                        version: APSC-DV-000010
                        rule_severity: medium
                      - id: 1800
                        rule_id: '000001'
                        title: The operating system must provide automated mechanisms
                        status: Not Yet Determined
                        component_id: 1
                    parents:
                      - id: 1
                        stig_id: Application_Security_Development_STIG
                        name: Application Security Development STIG - Ver 6, Rel 4
                        title: Application Security and Development STIG
                      - id: 1
                        name: Photon OS 3
                        prefix: PHOS-03
                        project:
                          id: 1
                          name: Photon 3
        '404':
          $ref: '#/components/responses/NotFound'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /rule_satisfactions:
    post:
      operationId: addSatisfaction
      tags:
        - Rules
      summary: Create a satisfaction relationship between two rules
      description: Links a child rule to a parent rule that satisfies it. Triggers ADNM status automation on the child.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - rule_id
                - satisfied_by_rule_id
              properties:
                rule_id:
                  type: integer
                  description: The rule that is satisfied. Absence is a 400.
                  example: 101
                satisfied_by_rule_id:
                  type: integer
                  description: The rule that satisfies it. Absence is a 400.
                  example: 102
      responses:
        '200':
          description: Satisfaction created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /rule_satisfactions/{ruleId}:
    parameters:
      - $ref: '#/components/parameters/RuleId'
    delete:
      operationId: removeSatisfaction
      tags:
        - Rules
      summary: Remove a satisfaction relationship
      description: Removes the parent-child satisfaction link and reverts ADNM status on the child rule.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                rule_id:
                  type: integer
                satisfied_by_rule_id:
                  type: integer
      responses:
        '200':
          description: Satisfaction removed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /rules/{ruleId}/relocations:
    parameters:
      - $ref: '#/components/parameters/RuleId'
    post:
      operationId: createRequirementRelocation
      tags:
        - Rules
      summary: Propose relocating an authored SRG requirement
      description: Creates an OPEN relocation proposal for an authored requirement of an SRG component, naming the destination SRG's abbreviation. Requires author role on the component. One open proposal per requirement; a second returns 422. The proposal is a record, never a status change on the requirement — the destination SRG's authors concur or non-concur.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - requirement_relocation
              properties:
                requirement_relocation:
                  type: object
                  required:
                    - target_technology_token
                  properties:
                    target_technology_token:
                      type: string
                      description: The destination SRG's abbreviation — the short code its requirement IDs start with (CTR, GPOS, DB).
                      example: CTR
            examples:
              mark:
                summary: Propose relocation to the Container SRG
                value:
                  requirement_relocation:
                    target_technology_token: CTR
      responses:
        '200':
          description: Relocation proposed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                proposed:
                  summary: Proposal created
                  value:
                    toast:
                      title: Relocation proposed.
                      message:
                        - Proposed for the CTR SRG.
                      variant: success
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          description: Duplicate open proposal or ineligible source
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                duplicate:
                  summary: Already proposed
                  value:
                    toast:
                      title: Could not propose the relocation.
                      message:
                        - Source rule already has a pending relocation
                      variant: danger
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /requirement_relocations:
    get:
      operationId: listRequirementRelocations
      tags:
        - Rules
      summary: List the open relocation-proposal backlog
      description: Returns relocation proposals, optionally filtered to one destination SRG's abbreviation — the standing per-SRG backlog and the creation/open-time prompt count both read this. Rows are scoped to projects the caller can see. Executed records are immutable history and never listed.
      parameters:
        - name: target_technology_token
          in: query
          required: false
          description: The destination SRG's abbreviation to filter by.
          schema:
            type: string
          example: CTR
      responses:
        '200':
          description: Open proposals (and retained declines), oldest first
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/RequirementRelocationSummary'
              examples:
                backlog:
                  summary: One open proposal for the CTR SRG
                  value:
                    - id: 12
                      source_rule_id: 5137
                      target_technology_token: CTR
                      created_at: 2026-07-20 15:36:06 UTC
                      source_displayed_name: CNTR-00-000051
                      component_id: 42
                      component_name: Container Platform SRG
                      requested_by_name: Jane Doe
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /requirement_relocations/destinations:
    get:
      operationId: listRelocationDestinations
      tags:
        - Rules
      summary: List destination SRG options for the propose flow
      description: 'Serves the Destination SRG picker: one row per SRG abbreviation across the SRG components in projects the caller can see (member or discoverable). An open component wins its abbreviation''s row; released true marks the queued next-release case. Hidden projects'' SRGs never appear — proposing to them remains possible through the free abbreviation entry, which discloses nothing.'
      responses:
        '200':
          description: Destination options, ordered by abbreviation
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/RelocationDestination'
              examples:
                destinations:
                  summary: One open component and one queued SRG
                  value:
                    - token: CTR
                      name: Container Platform SRG
                      released: false
                    - token: GPOS
                      name: General Purpose Operating System SRG
                      released: true
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /requirement_relocations/{id}:
    parameters:
      - name: id
        in: path
        required: true
        description: Numeric ID of the pending relocation record.
        schema:
          type: integer
        example: 12
    delete:
      operationId: deleteRequirementRelocation
      tags:
        - Rules
      summary: Un-mark a requirement for relocation
      description: Destroys a PENDING relocation record, removing the move marker. Audited. Requires author role on the source component. Executed records are immutable — they answer 404 exactly like a record that never existed.
      responses:
        '200':
          description: Marker removed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                unmarked:
                  summary: Marker removed
                  value:
                    toast:
                      title: Relocation marker removed.
                      message:
                        - The requirement is no longer marked for relocation.
                      variant: success
        '404':
          $ref: '#/components/responses/NotFound'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /requirement_relocations/{id}/dry_run:
    parameters:
      - name: id
        in: path
        required: true
        description: Numeric ID of the open relocation proposal.
        schema:
          type: integer
        example: 12
    post:
      operationId: dryRunRequirementRelocation
      tags:
        - Rules
      summary: Preview accepting a relocation with zero writes
      description: Returns exactly what accepting the proposal would do for this destination component — or every reason it cannot run — without writing anything. This preview is the adjudication review artifact. Requires author role on the DESTINATION component only. Adjudicated proposals (declined or executed) answer 404 like a record that never existed.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - target_component_id
              properties:
                target_component_id:
                  type: integer
                  description: The destination SRG component.
                  example: 42
            examples:
              preview:
                summary: Preview a move into component 42
                value:
                  target_component_id: 42
      responses:
        '200':
          description: The preview, valid or not
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RequirementRelocationDryRun'
              examples:
                valid:
                  summary: Move can run
                  value:
                    valid: true
                    errors: []
                    source_displayed_name: CNTR-00-000051
                    target_component_id: 42
                    target_component_name: Container Platform SRG
                    would_create:
                      title: The application must enforce approved authorizations
                      status: Applicable
                      derived_from_srg_rule_id: 5137
                    would_tombstone_source: true
        '404':
          $ref: '#/components/responses/NotFound'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /requirement_relocations/{id}/accept:
    parameters:
      - name: id
        in: path
        required: true
        description: Numeric ID of the open relocation proposal.
        schema:
          type: integer
        example: 12
    post:
      operationId: acceptRequirementRelocation
      tags:
        - Rules
      summary: Concur with a relocation proposal — land the requirement
      description: 'Receiver-side adjudication (displayed as Concur): acceptance and landing are ONE transaction — creates the requirement in the destination component with content and core lineage carried over, tombstones the source row, and stamps the record executed with the accepting actor — atomically, or not at all. Requires author role on the DESTINATION component only; the proposal itself carries source consent. Audited. Adjudicated proposals (declined or executed) answer 404 like a record that never existed.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - target_component_id
              properties:
                target_component_id:
                  type: integer
                  description: The destination SRG component.
                  example: 42
            examples:
              accept:
                summary: Concur — land the move into component 42
                value:
                  target_component_id: 42
      responses:
        '200':
          description: Proposal concurred with and requirement landed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RequirementRelocationAcceptResponse'
              examples:
                accepted:
                  summary: Move landed
                  value:
                    toast:
                      title: Concurred.
                      message:
                        - Moved to Container Platform SRG — the source requirement is now history.
                      variant: success
                    landed_rule_id: 5137
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          description: The move cannot run (validation errors from the preview)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                invalid:
                  summary: Ineligible destination
                  value:
                    toast:
                      title: Could not accept the proposal.
                      message:
                        - target component must be an SRG component
                      variant: danger
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /requirement_relocations/{id}/decline:
    parameters:
      - name: id
        in: path
        required: true
        description: Numeric ID of the open relocation proposal.
        schema:
          type: integer
        example: 12
    post:
      operationId: declineRequirementRelocation
      tags:
        - Rules
      summary: Non-concur with a relocation proposal, with a rationale
      description: 'Receiver-side adjudication (displayed as Non-concur): declines the proposal with a REQUIRED rationale. The record is retained as terminal history — never destroyed — and the rationale surfaces to the source author in the backlog, so the refusal communicates back across the ownership boundary. Requires author role on the destination component named in the request, and that component must be an ELIGIBLE receiver — an unreleased SRG component, other than the source, that declares the source requirement''s core SRG (the same eligibility accept enforces). Audited. A declined source may be proposed again.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - target_component_id
                - requirement_relocation
              properties:
                target_component_id:
                  type: integer
                  description: The destination component the decliner adjudicates for — establishes receiver-side authority.
                  example: 42
                requirement_relocation:
                  type: object
                  required:
                    - adjudication_rationale
                  properties:
                    adjudication_rationale:
                      type: string
                      description: Why the proposal is declined — shown to the source author.
                      example: Covered by CNTR-00-000001 already.
            examples:
              decline:
                summary: Decline with the reason
                value:
                  target_component_id: 42
                  requirement_relocation:
                    adjudication_rationale: Covered by CNTR-00-000001 already.
      responses:
        '200':
          description: Proposal non-concurred with and retained with the rationale
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                declined:
                  summary: Non-concur recorded
                  value:
                    toast:
                      title: Non-concurred.
                      message:
                        - The source author can see your rationale in the backlog.
                      variant: success
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          description: The rationale is missing, or the named component is not an eligible receiver
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                missing_rationale:
                  summary: Rationale required
                  value:
                    toast:
                      title: Could not decline the proposal.
                      message:
                        - Adjudication rationale can't be blank
                      variant: danger
                ineligible_receiver:
                  summary: Anchored to an ineligible component
                  value:
                    toast:
                      title: Could not decline the proposal.
                      message:
                        - target component must be an SRG component
                      variant: danger
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /rules/{ruleId}/reviews:
    parameters:
      - $ref: '#/components/parameters/RuleId'
    post:
      operationId: createRuleReview
      tags:
        - Reviews
      summary: Create a review on a rule (comment, request_review, etc.)
      description: Posts a new comment or review action on the specified rule. Requires viewer role or above on the component.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                review:
                  $ref: '#/components/schemas/ReviewInput'
      responses:
        '200':
          description: Review created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /components/{componentId}/reviews:
    parameters:
      - $ref: '#/components/parameters/ComponentId'
    post:
      operationId: createComponentReview
      tags:
        - Reviews
      summary: Post a public comment on a component
      description: Creates a new comment-action review on the component (component-level comment) or on a specific rule within it. The component must have an open comment period (accepting_new_comments? check). Requires project membership with at least viewer role.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                review:
                  $ref: '#/components/schemas/ReviewInput'
            examples:
              component_comment:
                summary: Post a component-level comment
                value:
                  review:
                    action: comment
                    comment: The overall approach to container isolation is sound.
                    section: null
              threaded_reply:
                summary: Reply to an existing component-level comment
                value:
                  review:
                    action: comment
                    comment: Agreed — the isolation model covers this case.
                    section: null
                    responding_to_review_id: 44
      responses:
        '200':
          description: Comment created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                created:
                  summary: Comment posted
                  value:
                    toast:
                      title: Comment posted.
                      message:
                        - Your comment has been submitted for review.
                      variant: success
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /reviews/bulk_triage:
    patch:
      operationId: bulkTriageReviews
      tags:
        - Reviews
      summary: Triage multiple reviews at once
      description: 'Applies the same triage status to all specified reviews in a single request. Parameters are FLAT (no wrapper object). All reviews must belong to the same component (enforced server-side). Terminal statuses (duplicate, informational, withdrawn, addressed_by) auto-adjudicate every selected review. Conditional requirements: non_concur requires response_comment; duplicate requires duplicate_of_review_id; addressed_by requires addressed_by_rule_id. The target is ONE shared value applied to every selected review — many comments duplicating one canonical thread, or addressed by one rule. A duplicate canonical that is itself among review_ids is rejected. Every per-comment validator runs per selected review (same-component canonical, no chained duplicates); any failing review rolls back the whole batch. Optionally creates a response comment on each triaged review. Requires author+ role on the component''s project.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - review_ids
                - triage_status
              properties:
                review_ids:
                  type: array
                  items:
                    type: integer
                  description: IDs of the reviews to triage.
                  example:
                    - 10
                    - 11
                    - 12
                triage_status:
                  type: string
                  description: The triage decision applied to all selected reviews. Every value except "pending" is accepted; "pending" is rejected as a non-decision.
                  enum:
                    - concur
                    - concur_with_comment
                    - non_concur
                    - needs_clarification
                    - duplicate
                    - informational
                    - withdrawn
                    - addressed_by
                  example: concur
                duplicate_of_review_id:
                  type: integer
                  description: REQUIRED when triage_status is "duplicate" — the one canonical review every selected review duplicates. Must be a comment in the same component, must not itself be a duplicate, and must not appear in review_ids.
                  example: 42
                addressed_by_rule_id:
                  type: integer
                  description: REQUIRED when triage_status is "addressed_by" — the one rule that addresses every selected review.
                  example: 7
                response_comment:
                  type: string
                  description: REQUIRED when triage_status is "non_concur"; optional otherwise. Posted as a reply to each triaged review.
                  example: Accepted — will update check_content in next release.
            examples:
              concur:
                summary: Accept several comments at once (flat body)
                value:
                  review_ids:
                    - 10
                    - 11
                    - 12
                  triage_status: concur
              duplicate:
                summary: Mark a group as duplicates of one canonical thread
                value:
                  review_ids:
                    - 10
                    - 11
                    - 12
                  triage_status: duplicate
                  duplicate_of_review_id: 42
              addressed_by:
                summary: Mark a group as addressed by one rule
                value:
                  review_ids:
                    - 10
                    - 11
                    - 12
                  triage_status: addressed_by
                  addressed_by_rule_id: 7
      responses:
        '200':
          description: All reviews triaged successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkTriageResponse'
              examples:
                triaged:
                  summary: Three reviews accepted
                  value:
                    reviews:
                      - id: 10
                        action: comment
                        comment: Original comment 1
                        triage_status: concur
                        rule_id: 100
                        created_at: 2026-05-19 14:08:17 UTC
                        reactions:
                          up: 0
                          down: 0
                          mine: null
                    response_reviews: []
        '422':
          description: Validation error — invalid or "pending" triage status, reviews spanning components, a missing conditional target, a duplicate canonical among review_ids, a cross-component or chained-duplicate canonical, or any selected review failing its per-comment validators (the batch rolls back atomically).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
  /reviews/merge:
    patch:
      operationId: mergeReviews
      tags:
        - Reviews
      summary: Merge duplicate reviews into one survivor
      description: Combines multiple same-author reviews within one component into a designated survivor. Non-survivor reviews are marked as duplicates (triage_status=duplicate, duplicate_of_review_id=survivor). Requires admin role on the component's project.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - review_ids
                - survivor_id
              properties:
                review_ids:
                  type: array
                  items:
                    type: integer
                  description: IDs of all reviews to merge (including the survivor).
                  example:
                    - 10
                    - 11
                    - 12
                survivor_id:
                  type: integer
                  description: ID of the review that absorbs the others.
                  example: 10
      responses:
        '200':
          description: Reviews merged successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MergeResponse'
              examples:
                merged:
                  summary: Two duplicates merged into survivor
                  value:
                    survivor:
                      id: 10
                      action: comment
                      comment: The original comment
                      triage_status: pending
                      rule_id: 100
                      created_at: 2026-05-19 14:08:17 UTC
                      reactions:
                        up: 0
                        down: 0
                        mine: null
                    duplicates:
                      - id: 11
                        action: comment
                        comment: Duplicate of 10
                        triage_status: duplicate
                        duplicate_of_review_id: 10
                        rule_id: 100
                        created_at: 2026-05-19 14:10:00 UTC
                        reactions:
                          up: 0
                          down: 0
                          mine: null
        '422':
          description: Validation error (reviews from different components, different authors, etc.)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
  /reviews/{reviewId}:
    parameters:
      - $ref: '#/components/parameters/ReviewId'
    put:
      operationId: updateReview
      tags:
        - Reviews
      summary: Update a review comment
      description: Edits the comment text of an existing review. Only the original author or an admin can update.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - review
              properties:
                review:
                  $ref: '#/components/schemas/ReviewUpdateInput'
      responses:
        '200':
          description: Review updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReviewWrapper'
          links:
            GetReviewResponses:
              operationId: getReviewResponses
              parameters:
                reviewId: $response.body#/review/id
            TriageReview:
              operationId: triageReview
              parameters:
                reviewId: $response.body#/review/id
            WithdrawReview:
              operationId: withdrawReview
              parameters:
                reviewId: $response.body#/review/id
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /reviews/{reviewId}/triage:
    parameters:
      - $ref: '#/components/parameters/ReviewId'
    patch:
      operationId: triageReview
      tags:
        - Reviews
      summary: Set triage status on a comment (concur, non_concur, etc.)
      description: 'Assigns a triage decision to the comment. Parameters are FLAT (no wrapper object). Terminal statuses (duplicate, informational, withdrawn, addressed_by) auto-adjudicate. Submitting "pending" is rejected — it is the initial state, not a decision. Conditional requirements: non_concur requires response_comment; duplicate requires duplicate_of_review_id; addressed_by requires addressed_by_rule_id. Requires author or admin role.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - triage_status
              properties:
                triage_status:
                  type: string
                  description: The triage decision. Every value except "pending" is accepted; "pending" is rejected as a non-decision.
                  enum:
                    - concur
                    - concur_with_comment
                    - non_concur
                    - needs_clarification
                    - duplicate
                    - informational
                    - withdrawn
                    - addressed_by
                  example: concur
                duplicate_of_review_id:
                  type: integer
                  description: REQUIRED when triage_status is "duplicate" — the canonical review this comment duplicates.
                  example: 42
                addressed_by_rule_id:
                  type: integer
                  description: REQUIRED when triage_status is "addressed_by" — the rule that addresses this comment.
                  example: 7
                response_comment:
                  type: string
                  description: REQUIRED when triage_status is "non_concur"; optional otherwise. Posted as a reply to the comment.
                  example: Declining — the requirement text already covers this.
            examples:
              concur:
                summary: Simple concur (flat body)
                value:
                  triage_status: concur
              non_concur:
                summary: Decline with the required response
                value:
                  triage_status: non_concur
                  response_comment: Declining — covered by the fix text.
      responses:
        '200':
          description: Triage status set
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TriageResponse'
          links:
            AdjudicateTriagedReview:
              operationId: adjudicateReview
              parameters:
                reviewId: $response.body#/review/id
              description: Close the review after triage is complete.
            ReopenTriagedReview:
              operationId: reopenReview
              parameters:
                reviewId: $response.body#/review/id
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /reviews/{reviewId}/adjudicate:
    parameters:
      - $ref: '#/components/parameters/ReviewId'
    patch:
      operationId: adjudicateReview
      tags:
        - Reviews
      summary: Adjudicate (close) a triaged comment
      description: Marks the comment as adjudicated, finalizing the triage decision. Requires author or admin role.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                resolution_comment:
                  type: string
                  description: Optional final-resolution text. When supplied, a child Review (action=comment, threaded via responding_to_review_id with the parent's section) is created atomically with the adjudication and returned as response_review, so the resolution renders inline in the comment thread.
                  example: Fix text updated in R2 — closing as concur.
      responses:
        '200':
          description: Review adjudicated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TriageResponse'
          links:
            ReopenAdjudicatedReview:
              operationId: reopenReview
              parameters:
                reviewId: $response.body#/review/id
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /reviews/{reviewId}/withdraw:
    parameters:
      - $ref: '#/components/parameters/ReviewId'
    patch:
      operationId: withdrawReview
      tags:
        - Reviews
      summary: Author withdraws their own comment
      description: Allows the comment author to retract their comment. Sets triage_status to withdrawn and auto-adjudicates.
      responses:
        '200':
          description: Review withdrawn
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReviewWrapper'
          links:
            AdminRestoreWithdrawnReview:
              operationId: adminRestoreReview
              parameters:
                reviewId: $response.body#/review/id
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /reviews/{reviewId}/admin_withdraw:
    parameters:
      - $ref: '#/components/parameters/ReviewId'
    patch:
      operationId: adminWithdrawReview
      tags:
        - Reviews
      summary: Admin force-withdraws a comment
      description: Admin-only force withdrawal that bypasses the frozen-for-writes check. Requires an audit comment explaining the action.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - audit_comment
              properties:
                audit_comment:
                  type: string
                  maxLength: 4096
                  description: Operator-supplied reason for the force-withdrawal, recorded on the audit trail. Required — a blank comment returns 422.
                  example: Removing PII discovered after the comment window closed.
      responses:
        '200':
          description: Review withdrawn by admin
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReviewWrapper'
          links:
            AdminRestoreWithdrawnReview:
              operationId: adminRestoreReview
              parameters:
                reviewId: $response.body#/review/id
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /reviews/{reviewId}/admin_restore:
    parameters:
      - $ref: '#/components/parameters/ReviewId'
    patch:
      operationId: adminRestoreReview
      tags:
        - Reviews
      summary: Admin restores an adjudicated comment to pending
      description: Inverse of admin_withdraw and any other adjudication — resets the comment to pending status so it can be re-triaged through the normal flow. Rejects comments that are not adjudicated (nothing to restore from). Requires admin role on the project and an audit comment explaining the action.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - audit_comment
              properties:
                audit_comment:
                  type: string
                  maxLength: 4096
                  description: Operator-supplied reason for the restore, recorded on the audit trail. Required — a blank comment returns 422.
                  example: Restoring — the wrong comment was force-withdrawn.
      responses:
        '200':
          description: Review restored
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReviewWrapper'
          links:
            TriageRestoredReview:
              operationId: triageReview
              parameters:
                reviewId: $response.body#/review/id
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /reviews/{reviewId}/move_to_rule:
    parameters:
      - $ref: '#/components/parameters/ReviewId'
    patch:
      operationId: moveReviewToRule
      tags:
        - Reviews
      summary: Admin moves a comment thread to a different rule
      description: Reassigns the comment and all its replies to a different requirement row in the same component. The target may be either document kind — a stig rule or an authored SRG requirement (the lookup spans both). Walks parent-first to satisfy validators and records an outbound audit entry on the source rule. Requires admin role.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - rule_id
                - audit_comment
              properties:
                rule_id:
                  type: integer
                  description: Database id of the target requirement row (either kind), which must belong to the same component as the source rule.
                  example: 15117
                audit_comment:
                  type: string
                  maxLength: 4096
                  description: Operator-supplied reason for the move, recorded on the audit trail. Required — a blank comment returns 422.
                  example: Comment applies to the account-management requirement.
            examples:
              move:
                summary: Move a thread to another requirement
                value:
                  rule_id: 15117
                  audit_comment: Comment applies to the account-management requirement.
      responses:
        '200':
          description: Review moved — returns the updated review.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReviewWrapper'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          description: Move refused — the target is the source rule itself, the target belongs to a different component, or the audit comment is blank.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                same_rule:
                  summary: Target is the source rule (no move performed)
                  value:
                    toast:
                      title: Cannot move.
                      message:
                        - Target rule is the same as the source rule.
                      variant: warning
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /reviews/{reviewId}/admin_destroy:
    parameters:
      - $ref: '#/components/parameters/ReviewId'
    delete:
      operationId: adminDestroyReview
      tags:
        - Reviews
      summary: Admin permanently deletes a comment (irreversible)
      description: Hard-deletes a comment and all its replies. Requires an audit comment explaining the action — the pre-destroy snapshot plus that comment become the audit record. (The web UI additionally asks the admin to type the comment ID to confirm; the API has no confirmation field.) This action cannot be undone.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - audit_comment
              properties:
                audit_comment:
                  type: string
                  maxLength: 4096
                  description: Operator-supplied reason for the hard-delete, recorded on the audit trail. Required — a blank comment returns 422.
                  example: Legal hard-delete — comment contained PII.
      responses:
        '200':
          description: Review destroyed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AdminDestroyResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /reviews/{reviewId}/responses:
    parameters:
      - $ref: '#/components/parameters/ReviewId'
    get:
      operationId: getReviewResponses
      tags:
        - Reviews
      summary: List replies to a review
      description: Returns threaded replies to a parent review, ordered by creation time. Each reply is serialized via ReviewBlueprint with commenter attribution, reaction counts, and the current user's reaction.
      responses:
        '200':
          description: Reply thread
          content:
            application/json:
              schema:
                type: object
                required:
                  - rows
                properties:
                  rows:
                    type: array
                    description: Reply rows ordered by created_at.
                    items:
                      $ref: '#/components/schemas/ReviewSummary'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /reviews/{reviewId}/reactions:
    parameters:
      - $ref: '#/components/parameters/ReviewId'
    get:
      operationId: listReactions
      tags:
        - Reactions
      summary: List reactions on a review
      description: Returns the reacting users' display names, grouped by reaction kind (up/down), for the specified review.
      responses:
        '200':
          description: Reactions list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReactionsSummary'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    post:
      operationId: createReaction
      tags:
        - Reactions
      summary: Add a reaction (up/down) to a comment review
      description: Toggles a thumbs-up or thumbs-down reaction. If the user already reacted with the same kind, the reaction is removed.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - kind
              properties:
                kind:
                  type: string
                  enum:
                    - up
                    - down
                  description: Reaction kind to toggle. Sent at the top level, not nested.
                  example: up
      responses:
        '200':
          description: Reaction created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReactionToggleResponse'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /reviews/{reviewId}/reopen:
    parameters:
      - $ref: '#/components/parameters/ReviewId'
    patch:
      operationId: reopenReview
      tags:
        - Reviews
      summary: Re-open an adjudicated review
      description: Reverts the adjudication on a closed review, clearing adjudicated_at and adjudicated_by_id so the triage decision can be revised. Requires author role or higher on the parent project. Returns 422 warning toast if the review has not been adjudicated or was withdrawn by the commenter.
      responses:
        '200':
          description: Review re-opened
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReviewWrapper'
          links:
            TriageReopenedReview:
              operationId: triageReview
              parameters:
                reviewId: $response.body#/review/id
            UpdateReopenedReview:
              operationId: updateReview
              parameters:
                reviewId: $response.body#/review/id
          examples:
            reopened:
              summary: Successfully re-opened
              value:
                review:
                  id: 44
                  action: comment
                  comment: This requirement needs clarification.
                  triage_status: pending
                  adjudicated_at: null
        '422':
          description: Cannot re-open (not adjudicated or withdrawn)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /reviews/{reviewId}/section:
    parameters:
      - $ref: '#/components/parameters/ReviewId'
    patch:
      operationId: updateReviewSection
      tags:
        - Reviews
      summary: Update the section field on a review
      description: 'Changes which requirement section (fixtext, check_content, vuln_discussion, etc.) a comment addresses. Idempotent — re-saving the same section returns the review with an `idempotent: true` flag. Requires author role or higher. An audit trail entry is created for non-idempotent changes.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - audit_comment
              properties:
                section:
                  type:
                    - string
                    - 'null'
                  description: Target section key from Review::SECTION_KEYS. Null for overall requirement.
                  enum:
                    - title
                    - severity
                    - status
                    - fixtext
                    - check_content
                    - vuln_discussion
                    - disa_metadata
                    - vendor_comments
                    - artifact_description
                    - xccdf_metadata
                    - null
                  example: fixtext
                audit_comment:
                  type: string
                  maxLength: 4096
                  description: Reason for the section change (audit trail). Required — a blank comment returns 422.
                  example: Commenter clarified this applies to the fix text.
            examples:
              change_section:
                summary: Move comment to vuln_discussion
                value:
                  section: vuln_discussion
                  audit_comment: Re-categorized after author clarification.
      responses:
        '200':
          description: Section updated (or idempotent no-op)
          content:
            application/json:
              schema:
                type: object
                required:
                  - review
                properties:
                  review:
                    $ref: '#/components/schemas/ReviewSummary'
                  idempotent:
                    type: boolean
                    description: Present and true when the section was already the requested value.
              examples:
                changed:
                  summary: Section updated
                  value:
                    review:
                      id: 44
                      action: comment
                      comment: This requirement needs clarification...
                      rule_id: 812
                      section: vuln_discussion
                      triage_status: pending
                      created_at: '2026-05-19T16:15:00Z'
                      reactions:
                        up: 1
                        down: 0
                idempotent:
                  summary: No change — same section
                  value:
                    review:
                      id: 44
                      action: comment
                      comment: This requirement needs clarification...
                      rule_id: 812
                      section: fixtext
                      triage_status: pending
                      created_at: '2026-05-19T16:15:00Z'
                      reactions:
                        up: 1
                        down: 0
                    idempotent: true
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /memberships:
    post:
      operationId: createMembership
      tags:
        - Memberships
      summary: Add a user to a project or component
      description: Creates a membership linking a user to a project or component with the specified role (viewer, author, reviewer, admin). Requires admin role on the target project. Duplicate memberships are rejected.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                membership:
                  $ref: '#/components/schemas/MembershipInput'
            examples:
              add_author:
                summary: Add a user as author
                value:
                  membership:
                    user_id: 42
                    membership_id: 4
                    membership_type: Project
                    role: author
      responses:
        '200':
          description: Membership created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                added:
                  summary: User added to project
                  value:
                    toast:
                      title: Member added.
                      message:
                        - Jane Doe added as author.
                      variant: success
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /memberships/{membershipId}:
    parameters:
      - $ref: '#/components/parameters/MembershipId'
    patch:
      operationId: patchMembership
      tags:
        - Memberships
      summary: Partial update of membership role
      description: Partial update — send only the fields to change. Changes the role (viewer, author, reviewer, admin) for an existing membership. Requires admin role on the parent project. Cannot demote the last admin on a project.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                membership:
                  $ref: '#/components/schemas/MembershipUpdateInput'
            examples:
              promote:
                summary: Promote to reviewer
                value:
                  membership:
                    role: reviewer
      responses:
        '200':
          description: Membership updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                promoted:
                  summary: Role changed
                  value:
                    toast:
                      title: Role updated.
                      message:
                        - Jane Doe is now a reviewer.
                      variant: success
        '422':
          description: Validation failed — e.g. downgrading the project's last admin
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                last_admin:
                  summary: Last project admin cannot be downgraded
                  value:
                    toast:
                      title: Could not update membership.
                      message:
                        - Role cannot be changed — this user is the last admin of project 'Photon OS 5'. Transfer the admin role to another member first.
                      variant: danger
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    put:
      operationId: updateMembership
      tags:
        - Memberships
      summary: Full replacement of membership role
      description: Full replacement — all fields required. Changes the role (viewer, author, reviewer, admin) for an existing membership. Requires admin role on the parent project. Cannot demote the last admin on a project.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                membership:
                  $ref: '#/components/schemas/MembershipUpdateInput'
            examples:
              promote:
                summary: Promote to reviewer
                value:
                  membership:
                    role: reviewer
      responses:
        '200':
          description: Membership updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                promoted:
                  summary: Role changed
                  value:
                    toast:
                      title: Role updated.
                      message:
                        - Jane Doe is now a reviewer.
                      variant: success
        '422':
          description: Validation failed — e.g. downgrading the project's last admin
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                last_admin:
                  summary: Last project admin cannot be downgraded
                  value:
                    toast:
                      title: Could not update membership.
                      message:
                        - Role cannot be changed — this user is the last admin of project 'Photon OS 5'. Transfer the admin role to another member first.
                      variant: danger
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    delete:
      operationId: deleteMembership
      tags:
        - Memberships
      summary: Remove a user from a project or component
      description: Deletes the membership, revoking the user's access. Requires admin role on the parent project. Removed users lose authority over all project-scoped resources including their own pending comments. The last admin of a project cannot be removed — transfer the admin role first.
      responses:
        '200':
          description: Membership removed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                removed:
                  summary: User removed from project
                  value:
                    toast:
                      title: Member removed.
                      message:
                        - Jane Doe removed from project.
                      variant: success
        '422':
          description: Removal blocked — the project's last admin
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                last_admin:
                  summary: Last project admin cannot be removed
                  value:
                    toast:
                      title: Could not remove membership.
                      message:
                        - Cannot remove the last admin of project 'Photon OS 5'. Transfer the admin role to another member first.
                      variant: danger
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /srgs:
    get:
      operationId: listSrgs
      tags:
        - Benchmarks
      summary: List uploaded Security Requirements Guides
      description: Returns all uploaded SRGs sorted by title. Requires authentication. SRGs are the DISA baseline requirement documents that Components implement. Each SRG contains the rules that Component authors map their controls to.
      responses:
        '200':
          description: All SRGs
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/SrgSummary'
              examples:
                srgs:
                  summary: Two SRGs
                  value:
                    - id: 1
                      srg_id: Container_Platform_SRG
                      name: Container Platform SRG - Ver 2, Rel 4
                      title: Container Platform Security Requirements Guide
                      version: V2R4
                      release_date: '2025-10-28'
                      core: false
                      severity_counts:
                        high: 8
                        medium: 177
                        low: 3
                    - id: 2
                      srg_id: General_Purpose_Operating_System_SRG
                      name: General Purpose Operating System - Ver 3, Rel 3
                      title: General Purpose Operating System SRG
                      version: V3R3
                      release_date: '2025-06-15'
                      core: false
                      severity_counts:
                        high: 15
                        medium: 150
                        low: 8
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    post:
      operationId: uploadSrg
      tags:
        - Benchmarks
      summary: Upload an SRG XCCDF XML file
      description: Parses and imports a DISA SRG from an XCCDF XML file. Requires admin role. Extracts all rules, CCIs, and metadata from the XML. Duplicate SRGs (same title + version) are rejected.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - file
              properties:
                file:
                  type: string
                  format: binary
                  description: DISA XCCDF XML file (.xml) containing the SRG.
      responses:
        '200':
          description: SRG uploaded and parsed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                created:
                  summary: SRG imported
                  value:
                    toast:
                      title: SRG created.
                      message:
                        - Successfully created SRG.
                      variant: success
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /srgs/{id}:
    parameters:
      - $ref: '#/components/parameters/SrgId'
    get:
      operationId: getSrg
      tags:
        - Benchmarks
      summary: SRG detail with rules and metadata
      description: Returns full SRG details including title, version, release date, severity counts, and all embedded rules with DISA metadata and check content. Requires authentication. Used by the SRG detail page (BenchmarkViewer).
      responses:
        '200':
          description: SRG detail with nested rules
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SrgDetailResponse'
              examples:
                srg:
                  summary: Container Platform SRG
                  value:
                    id: 1
                    srg_id: Container_Platform_SRG
                    name: Container Platform SRG - Ver 2, Rel 4
                    title: Container Platform Security Requirements Guide
                    version: V2R4
                    release_date: '2025-10-28'
                    core: false
                    severity_counts:
                      high: 8
                      medium: 177
                      low: 3
                    srg_rules:
                      - id: 1
                        rule_id: SV-233015r960759_rule
                        title: The container platform must use TLS 1.2 or greater...
                        version: SRG-APP-000014-CTR-000035
                        rule_severity: medium
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    delete:
      operationId: deleteSrg
      tags:
        - Benchmarks
      summary: Delete an uploaded SRG (admin only)
      description: Permanently deletes an SRG and all its embedded rules. Requires admin role. Components based on this SRG will lose their baseline reference. This action cannot be undone.
      responses:
        '200':
          description: SRG removed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                removed:
                  summary: SRG removed
                  value:
                    toast:
                      title: SRG removed.
                      message:
                        - Successfully removed SRG.
                      variant: success
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /srgs/{id}/export/{type}:
    parameters:
      - $ref: '#/components/parameters/SrgId'
      - name: type
        in: path
        required: true
        description: Export format.
        schema:
          type: string
          enum:
            - csv
            - xccdf
        example: xccdf
    get:
      operationId: exportSrg
      tags:
        - Benchmarks
      summary: Export SRG in the specified format
      description: Downloads the SRG as a CSV spreadsheet or XCCDF XML file. Requires authentication. CSV exports all rules in a flat table. XCCDF exports the original DISA XML format.
      responses:
        '200':
          description: Binary file download
          content:
            text/csv:
              schema:
                type: string
                format: binary
            application/xml:
              schema:
                type: string
                format: binary
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /stigs:
    get:
      operationId: listStigs
      tags:
        - Benchmarks
      summary: List uploaded STIGs
      description: Returns all uploaded STIGs sorted by title. Requires authentication. STIGs are published security guidance that can be used as reference when authoring Components.
      responses:
        '200':
          description: All STIGs
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/StigSummary'
              examples:
                stigs:
                  summary: Two STIGs
                  value:
                    - id: 1
                      stig_id: Application_Security_Development_STIG
                      name: Application Security Development STIG - Ver 6, Rel 4
                      title: Application Security and Development Security Technical Implementation Guide
                      version: V6R4
                      benchmark_date: '2025-10-01'
                      severity_counts:
                        high: 34
                        medium: 230
                        low: 22
                    - id: 2
                      stig_id: Crunchy_Data_PostgreSQL_STIG
                      name: Crunchy Data PostgreSQL STIG - Ver 3, Rel 1
                      title: Crunchy Data PostgreSQL Security Technical Implementation Guide
                      version: V3R1
                      benchmark_date: '2025-09-15'
                      severity_counts:
                        high: 10
                        medium: 85
                        low: 5
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    post:
      operationId: uploadStig
      tags:
        - Benchmarks
      summary: Upload a STIG XCCDF XML file
      description: Parses and imports a published STIG from an XCCDF XML file. Requires admin role. Extracts all rules, CCIs, and metadata. Duplicate STIGs (same title + version) are rejected.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - file
              properties:
                file:
                  type: string
                  format: binary
                  description: DISA XCCDF XML file (.xml) containing the STIG.
      responses:
        '200':
          description: STIG uploaded and parsed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                added:
                  summary: STIG imported
                  value:
                    toast:
                      title: STIG added.
                      message:
                        - Successfully added Application Security and Development Security Technical Implementation Guide.
                      variant: success
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /stigs/{id}:
    parameters:
      - $ref: '#/components/parameters/StigId'
    get:
      operationId: getStig
      tags:
        - Benchmarks
      summary: STIG detail with rules and metadata
      description: Returns full STIG details including title, version, benchmark date, description, severity counts, and all embedded rules with DISA metadata and check content. Requires authentication. Used by the STIG detail page (BenchmarkViewer).
      responses:
        '200':
          description: STIG detail with nested rules
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StigDetailResponse'
              examples:
                stig:
                  summary: Application Security Development STIG
                  value:
                    id: 1
                    stig_id: Application_Security_Development_STIG
                    name: Application Security Development STIG - Ver 6, Rel 4
                    title: Application Security and Development Security Technical Implementation Guide
                    version: V6R4
                    benchmark_date: '2025-10-01'
                    severity_counts:
                      high: 34
                      medium: 230
                      low: 22
                    description: This Security Technical Implementation Guide is published...
                    stig_rules:
                      - id: 660
                        rule_id: SV-222387r960735_rule
                        title: The application must limit logon sessions...
                        version: APSC-DV-000010
                        rule_severity: medium
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    delete:
      operationId: deleteStig
      tags:
        - Benchmarks
      summary: Delete an uploaded STIG (admin only)
      description: Permanently deletes a STIG and all its embedded rules. Requires admin role. This action cannot be undone.
      responses:
        '200':
          description: STIG removed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                removed:
                  summary: STIG removed
                  value:
                    toast:
                      title: STIG removed.
                      message:
                        - Successfully removed Application Security and Development Security Technical Implementation Guide.
                      variant: success
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /stigs/{id}/export/{type}:
    parameters:
      - $ref: '#/components/parameters/StigId'
      - name: type
        in: path
        required: true
        description: Export format. STIGs support CSV and XCCDF only (InSpec export is Components-only).
        schema:
          type: string
          enum:
            - csv
            - xccdf
        example: xccdf
    get:
      operationId: exportStig
      tags:
        - Benchmarks
      summary: Export STIG in the specified format
      description: Downloads the STIG as CSV or XCCDF XML. Requires authentication. CSV exports all rules in a flat table. XCCDF exports the original DISA XML format.
      responses:
        '200':
          description: Binary file download
          content:
            text/csv:
              schema:
                type: string
                format: binary
            application/xml:
              schema:
                type: string
                format: binary
        '400':
          description: Unsupported export type
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /users:
    get:
      operationId: listUsers
      tags:
        - Users
      summary: List all users (admin only)
      description: Returns all user accounts sorted alphabetically. Includes login tracking fields (last_sign_in_at, failed_attempts, locked_at) for admin monitoring. Requires admin role — returns 403 for non-admin users.
      responses:
        '200':
          description: All user accounts
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/UserSummary'
              examples:
                users:
                  summary: Two users
                  value:
                    - id: 1
                      name: Demo Admin
                      email: admin@example.com
                      provider: null
                      admin: true
                      last_sign_in_at: '2026-05-28T15:00:00Z'
                      failed_attempts: 0
                      locked_at: null
                    - id: 5
                      name: Bernice Deckow
                      email: bernice.deckow@example.com
                      provider: null
                      admin: false
                      last_sign_in_at: null
                      failed_attempts: 0
                      locked_at: null
        '403':
          $ref: '#/components/responses/Forbidden'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    put:
      operationId: updateProfile
      tags:
        - Users
      summary: Update the signed-in user's own profile
      description: 'Updates the current user''s profile (Devise registration update). Field-sensitivity policy: name and slack_user_id save without a password; changing the email — the login identifier — requires current_password (re-authentication for sensitive changes). When email confirmation is enabled, an email change is held in unconfirmed_email until the confirmation link is followed; otherwise it applies immediately. Provider-managed users (OIDC/LDAP) cannot change email here — the identity provider owns it and the parameter is ignored.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - user
              properties:
                user:
                  type: object
                  description: Profile fields to update.
                  properties:
                    name:
                      type: string
                      description: Display name.
                      example: Jane Doe
                    email:
                      type: string
                      format: email
                      description: New login email. Requires current_password when it differs from the current address. Ignored for provider-managed users.
                      example: jane@example.com
                    slack_user_id:
                      type: string
                      description: Slack user ID for notifications.
                      example: U123456
                    current_password:
                      type: string
                      description: The user's current password — required only when changing the email.
                      example: MyCurrentP@ssw0rd!
            examples:
              rename:
                summary: Passwordless non-sensitive save
                value:
                  user:
                    name: Jane Doe
                    slack_user_id: U123456
              email_change:
                summary: Email change with re-authentication
                value:
                  user:
                    email: new-address@example.com
                    current_password: MyCurrentP@ssw0rd!
      responses:
        '200':
          description: Profile updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                updated:
                  summary: Successful update
                  value:
                    toast:
                      title: Account updated.
                      message:
                        - Profile updated successfully.
                      variant: success
                confirmation_pending:
                  summary: Email change held for confirmation
                  value:
                    toast:
                      title: Account updated.
                      message:
                        - A confirmation link has been sent to new-address@example.com. Please follow the link to verify your new email address.
                      variant: success
        '422':
          description: Validation failed (e.g. email change without the current password)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                missing_password:
                  summary: Email change without current password
                  value:
                    toast:
                      title: Could not update profile.
                      message:
                        - Current password can't be blank
                      variant: danger
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    delete:
      operationId: deleteOwnAccount
      tags:
        - Auth
      summary: Delete the signed-in user's own account
      description: Permanently deletes the current user's account and signs them out. Local-credential users must re-authenticate with current_password (OWASP ASVS 3.7.1); provider-managed and SSO-created accounts are exempt — their identity provider owns re-authentication. Blocked with 422 when the user is the only system administrator or the only admin of any project (transfer the admin role first). Repeated wrong passwords count toward account lockout and return 423 once locked.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                user:
                  type: object
                  properties:
                    current_password:
                      type: string
                      format: password
                      description: The user's current password. Required for local-credential accounts; ignored for provider-managed accounts.
                      example: MyCurrentP@ssw0rd!
            examples:
              local_user:
                summary: Local-credential user re-authenticates
                value:
                  user:
                    current_password: MyCurrentP@ssw0rd!
      responses:
        '200':
          description: Account deleted and session ended
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                deleted:
                  summary: Successful self-delete
                  value:
                    toast:
                      title: Account deleted.
                      message:
                        - Account deleted successfully.
                      variant: success
        '422':
          description: Wrong/missing password, or user is the only system administrator
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                wrong_password:
                  summary: Re-authentication failed
                  value:
                    toast:
                      title: Cannot delete account.
                      message:
                        - Incorrect password. Please enter your current password to delete your account.
                      variant: danger
                last_admin:
                  summary: Only system administrator
                  value:
                    toast:
                      title: Cannot delete account.
                      message:
                        - You are the only administrator. Promote another user to admin before deleting your account.
                      variant: danger
                sole_project_admin:
                  summary: Only admin of a project
                  value:
                    toast:
                      title: Cannot delete account.
                      message:
                        - 'You are the only admin of: ''Photon OS 5''. Transfer the admin role to another member of each project first.'
                      variant: danger
        '423':
          description: Account locked by repeated failed re-authentication attempts
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                locked:
                  summary: Locked mid-request
                  value:
                    toast:
                      title: Cannot delete account.
                      message:
                        - Your account has been locked due to too many failed attempts. Please try again later.
                      variant: danger
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /users/{userId}:
    parameters:
      - $ref: '#/components/parameters/UserId'
    patch:
      operationId: patchUser
      tags:
        - Users
      summary: Partial update of user attributes (admin only)
      description: Partial update — send only changed fields. Updates a user's name, email, or admin status. Requires admin role. Prevents the last admin from demoting themselves. Sends a Slack notification when admin status changes (if Slack is configured). Returns both a toast and the updated user object.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                user:
                  type: object
                  properties:
                    name:
                      type: string
                    email:
                      type: string
                      format: email
                    admin:
                      type: boolean
                    slack_user_id:
                      type: string
                      description: Slack member ID driving the user's Slack notifications. Send an empty string to clear it. Admin-made changes are audited.
                      example: U0123456789
            examples:
              promote:
                summary: Promote user to admin
                value:
                  user:
                    admin: true
              set_slack:
                summary: Set the user's Slack member ID
                value:
                  user:
                    name: Jane Doe
                    email: jane.doe@example.org
                    admin: false
                    slack_user_id: U0123456789
      responses:
        '200':
          description: User updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserToastResponse'
              examples:
                updated:
                  summary: User promoted to admin
                  value:
                    toast:
                      title: User updated.
                      message:
                        - Successfully updated user.
                      variant: success
                    user:
                      id: 42
                      name: Jane Doe
                      email: jane.doe@example.org
                      admin: true
        '422':
          description: Cannot remove last admin
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                last_admin:
                  summary: Cannot demote last admin
                  value:
                    toast:
                      title: Cannot remove admin.
                      message:
                        - You are the only admin. Promote another user first.
                      variant: danger
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    put:
      operationId: updateUser
      tags:
        - Users
      summary: Full replacement of user attributes (admin only)
      description: Full replacement — all fields required. Updates a user's name, email, or admin status. Requires admin role. Prevents the last admin from demoting themselves. Sends a Slack notification when admin status changes (if Slack is configured). Returns both a toast and the updated user object.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                user:
                  type: object
                  properties:
                    name:
                      type: string
                    email:
                      type: string
                      format: email
                    admin:
                      type: boolean
                    slack_user_id:
                      type: string
                      description: Slack member ID driving the user's Slack notifications. Send an empty string to clear it. Admin-made changes are audited.
                      example: U0123456789
            examples:
              promote:
                summary: Promote user to admin
                value:
                  user:
                    admin: true
              set_slack:
                summary: Set the user's Slack member ID
                value:
                  user:
                    name: Jane Doe
                    email: jane.doe@example.org
                    admin: false
                    slack_user_id: U0123456789
      responses:
        '200':
          description: User updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserToastResponse'
              examples:
                updated:
                  summary: User promoted to admin
                  value:
                    toast:
                      title: User updated.
                      message:
                        - Successfully updated user.
                      variant: success
                    user:
                      id: 42
                      name: Jane Doe
                      email: jane.doe@example.org
                      admin: true
        '422':
          description: Cannot remove last admin
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                last_admin:
                  summary: Cannot demote last admin
                  value:
                    toast:
                      title: Cannot remove admin.
                      message:
                        - You are the only admin. Promote another user first.
                      variant: danger
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    delete:
      operationId: deleteUser
      tags:
        - Users
      summary: Delete a user (admin only)
      description: Permanently deletes a user account. Requires admin role. Cannot delete the last remaining site admin, nor a user who is the only admin of any project — transfer the admin role first. All project memberships and associated data are cleaned up via dependent destroy.
      responses:
        '200':
          description: User removed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                removed:
                  summary: User removed
                  value:
                    toast:
                      title: User removed.
                      message:
                        - Successfully removed user.
                      variant: success
        '422':
          description: Deletion blocked — the last site admin, or the only admin of one or more projects
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                last_site_admin:
                  summary: Only site admin
                  value:
                    toast:
                      title: Cannot delete user.
                      message:
                        - This is the only admin. Promote another user first.
                      variant: danger
                sole_project_admin:
                  summary: Only admin of a project
                  value:
                    toast:
                      title: Cannot delete user.
                      message:
                        - 'This user is the only admin of: ''Photon OS 5''. Transfer the admin role to another member of each project first.'
                      variant: danger
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /users/{userId}/comments:
    parameters:
      - $ref: '#/components/parameters/UserId'
    get:
      operationId: getUserComments
      tags:
        - Users
      summary: My Comments page — user's comments across accessible projects
      description: Returns paginated comments authored by the specified user across all projects the requesting user can access. Scoped by project visibility, not identity — this is a filtered view, not a privacy boundary. Supports triage status filtering, project filtering, and pagination.
      parameters:
        - name: triage_status
          in: query
          required: false
          description: Filter the user's comments by triage disposition. Absent or "all" returns comments in any status — this page has no default filter.
          schema:
            type: string
            enum:
              - all
              - pending
              - concur
              - concur_with_comment
              - non_concur
              - duplicate
              - informational
              - needs_clarification
              - withdrawn
              - addressed_by
            default: all
          example: concur
        - $ref: '#/components/parameters/PageParam'
        - $ref: '#/components/parameters/PerPageParam'
        - name: project_id
          in: query
          required: false
          description: Filter comments to a specific project.
          schema:
            type: integer
          example: 3
      responses:
        '200':
          description: Paginated user comments with project/component context
          content:
            application/json:
              schema:
                type: object
                required:
                  - rows
                  - pagination
                additionalProperties: false
                properties:
                  rows:
                    type: array
                    description: Comment rows for the current page.
                    items:
                      $ref: '#/components/schemas/UserCommentRow'
                  pagination:
                    type: object
                    required:
                      - page
                      - per_page
                      - total
                    additionalProperties: false
                    properties:
                      page:
                        type: integer
                        description: Current page number (1-based).
                        example: 1
                      per_page:
                        type: integer
                        description: Number of comments per page.
                        example: 25
                      total:
                        type: integer
                        description: Total number of matching comments.
                        example: 12
              examples:
                comments:
                  summary: User's pending comments across projects
                  value:
                    rows:
                      - id: 29
                        project_id: 3
                        project_name: vSphere 7.0
                        component_id: 4
                        component_name: Photon OS 3
                        rule_id: 2397
                        rule_displayed_name: PHOS-03-000039
                        commentable_type: BaseRule
                        section: fixtext
                        comment: 'vSphere 7.0: fix command targets ESXi 6.7 path.'
                        created_at: 2026-05-19 14:08:18 UTC
                        triage_status: pending
                        responses_count: 0
                        reactions:
                          up: 0
                          down: 0
                          mine: null
                    pagination:
                      page: 1
                      per_page: 25
                      total: 12
        '404':
          $ref: '#/components/responses/NotFound'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /users/unlink_identity:
    post:
      operationId: unlinkIdentity
      tags:
        - Users
      summary: Unlink an OAuth provider from the current user
      description: Removes an OAuth provider link (GitHub, LDAP, OIDC) from the current user's account. Requires the user's current password to confirm. The user must have local login credentials before unlinking to avoid being locked out. Available from the user profile page.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - current_password
                - provider
              properties:
                current_password:
                  type: string
                  description: Current password to confirm the action.
                  example: MyCurrentP@ssw0rd!
                provider:
                  type: string
                  description: OAuth provider to unlink.
                  enum:
                    - github
                    - ldap
                    - oidc
                  example: github
            examples:
              unlink_github:
                summary: Unlink GitHub provider
                value:
                  current_password: MyCurrentP@ssw0rd!
                  provider: github
      responses:
        '200':
          description: Provider unlinked
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                unlinked:
                  summary: GitHub unlinked
                  value:
                    toast:
                      title: Provider unlinked.
                      message:
                        - GitHub identity removed from your account.
                      variant: success
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /users/admin_create:
    post:
      operationId: adminCreateUser
      tags:
        - Users
      summary: Create a new user (admin only)
      description: 'Creates a new user account. Requires admin role. Three modes based on password and SMTP config: (1) password provided — user can sign in immediately, (2) no password + SMTP enabled — sends setup email via Devise, (3) no password + no SMTP — returns a reset URL the admin delivers manually. Skips email confirmation.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                user:
                  type: object
                  required:
                    - email
                  properties:
                    name:
                      type: string
                      description: Display name for the new user.
                      example: Jane Doe
                    email:
                      type: string
                      format: email
                      description: Login email address. Must be unique.
                      example: jane.doe@example.org
                    admin:
                      type: boolean
                      description: Whether to grant admin privileges.
                      example: false
                    slack_user_id:
                      type: string
                      description: Slack member ID driving the new user's Slack notifications. Optional; the user can change it later. Admin-made changes are audited.
                      example: U0123456789
                    password:
                      type: string
                      description: Initial password. If omitted, a reset link is generated.
                      example: SecureP@ssw0rd2026!
            examples:
              with_password:
                summary: Create with explicit password
                value:
                  user:
                    name: Jane Doe
                    email: jane.doe@example.org
                    admin: false
                    password: SecureP@ssw0rd2026!
              without_password:
                summary: Create without password (reset link generated)
                value:
                  user:
                    name: Jane Doe
                    email: jane.doe@example.org
      responses:
        '200':
          description: User created (response varies by mode)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AdminCreateResponse'
              examples:
                with_password:
                  summary: Created with provided password
                  value:
                    toast:
                      title: User created.
                      message:
                        - User jane.doe@example.org created with the provided password.
                      variant: success
                    user:
                      id: 42
                      name: Jane Doe
                      email: jane.doe@example.org
                      admin: false
                no_smtp:
                  summary: Created without SMTP — reset URL returned
                  value:
                    toast:
                      title: User created.
                      message:
                        - User jane.doe@example.org created. Deliver the reset link to the user.
                      variant: success
                    user:
                      id: 42
                      name: Jane Doe
                      email: jane.doe@example.org
                      admin: false
                    reset_url: https://vulcan.example.org/users/password/edit?reset_password_token=abc123
        '422':
          description: Validation error (duplicate email, invalid fields)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                duplicate:
                  summary: Email already taken
                  value:
                    toast:
                      title: Could not create user.
                      message:
                        - Email has already been taken
                      variant: danger
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /users/{userId}/send_password_reset:
    parameters:
      - $ref: '#/components/parameters/UserId'
    post:
      operationId: sendPasswordReset
      tags:
        - Users
      summary: Send Devise password reset email (admin only)
      description: Triggers a Devise password reset email to the specified user. Requires admin role and SMTP to be configured. Returns 422 if SMTP is disabled — use the generate_reset_link endpoint instead. Returns 500 if email delivery fails.
      responses:
        '200':
          description: Reset email sent successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                sent:
                  summary: Email delivered
                  value:
                    toast:
                      title: Reset email sent.
                      message:
                        - Password reset email sent to jane.doe@example.org.
                      variant: success
        '422':
          description: SMTP not configured
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                no_smtp:
                  summary: SMTP disabled
                  value:
                    toast:
                      title: SMTP not configured.
                      message:
                        - Email delivery is not available. Use "Generate Reset Link" instead.
                      variant: danger
        '500':
          description: Email delivery failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /users/{userId}/generate_reset_link:
    parameters:
      - $ref: '#/components/parameters/UserId'
    post:
      operationId: generateResetLink
      tags:
        - Users
      summary: Generate a password reset URL without sending email (admin only)
      description: Generates a Devise reset token and returns the full reset URL. Does not send email — the admin copies the link and delivers it to the user through a secure channel. Requires admin role. Works regardless of SMTP config.
      responses:
        '200':
          description: Reset link generated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResetLinkResponse'
              examples:
                link:
                  summary: Reset link generated
                  value:
                    toast:
                      title: Reset link generated.
                      message:
                        - Reset link generated. Copy it and deliver to the user.
                      variant: success
                    reset_url: https://vulcan.example.org/users/password/edit?reset_password_token=abc123def456
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /users/{userId}/set_password:
    parameters:
      - $ref: '#/components/parameters/UserId'
    post:
      operationId: setPassword
      tags:
        - Users
      summary: Directly set a user's password (admin only)
      description: Sets the password for a user account without requiring the old password. Requires admin role. The password must meet the configured minimum length (default 15 characters). Does not send any email notification. Used for urgent password resets when SMTP is unavailable.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                user:
                  type: object
                  required:
                    - password
                  properties:
                    password:
                      type: string
                      description: New password (must meet minimum length requirement).
                      example: SecureP@ssw0rd2026!
            examples:
              set:
                summary: Set a new password
                value:
                  user:
                    password: SecureP@ssw0rd2026!
      responses:
        '200':
          description: Password updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                success:
                  summary: Password set successfully
                  value:
                    toast:
                      title: Password updated.
                      message:
                        - Password updated for jane.doe@example.org.
                      variant: success
        '422':
          description: Blank password or Devise validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                blank:
                  summary: Empty password
                  value:
                    toast:
                      title: Password required.
                      message:
                        - Password cannot be blank.
                      variant: danger
                too_short:
                  summary: Password too short
                  value:
                    toast:
                      title: Could not set password.
                      message:
                        - Password is too short (minimum is 15 characters)
                      variant: danger
        '500':
          description: Internal error during password update
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /users/{userId}/lock:
    parameters:
      - $ref: '#/components/parameters/UserId'
    post:
      operationId: lockUser
      tags:
        - Users
      summary: Lock a user account (admin only)
      description: Prevents the user from signing in. Requires admin role. Returns 422 if the admin attempts to lock their own account. Creates an audit trail entry recording who locked the account and when.
      responses:
        '200':
          description: Account locked successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserToastResponse'
              examples:
                locked:
                  summary: Account locked
                  value:
                    toast:
                      title: Account locked.
                      message:
                        - Account jane.doe@example.org locked.
                      variant: success
                    user:
                      id: 42
                      name: Jane Doe
                      email: jane.doe@example.org
                      admin: false
                      locked_at: '2026-05-28T15:00:00Z'
        '422':
          description: Cannot lock own account
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                self_lock:
                  summary: Admin tried to lock themselves
                  value:
                    toast:
                      title: Cannot lock yourself.
                      message:
                        - You cannot lock your own account.
                      variant: danger
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /users/{userId}/unlock:
    parameters:
      - $ref: '#/components/parameters/UserId'
    post:
      operationId: unlockUser
      tags:
        - Users
      summary: Unlock a locked user account (admin only)
      description: Restores sign-in access for a locked user account. Requires admin role. Clears failed_attempts counter and locked_at timestamp. Creates an audit trail entry recording who unlocked the account.
      responses:
        '200':
          description: Account unlocked
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserToastResponse'
              examples:
                unlocked:
                  summary: Account unlocked
                  value:
                    toast:
                      title: Account unlocked.
                      message:
                        - Account jane.doe@example.org unlocked.
                      variant: success
                    user:
                      id: 42
                      name: Jane Doe
                      email: jane.doe@example.org
                      admin: false
                      locked_at: null
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /users/edit:
    get:
      operationId: getProfile
      tags:
        - Auth
      summary: Get current user profile (Devise edit)
      description: Returns the authenticated user's profile as JSON. This is the Devise registration edit endpoint with JSON support. The SPA may prefer GET /api/auth/me which returns the same CurrentUserResponse shape. Requires authentication.
      responses:
        '200':
          description: Current user profile
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CurrentUserResponse'
              examples:
                profile:
                  summary: User profile
                  value:
                    id: 42
                    name: Jane Doe
                    email: jane@example.com
                    admin: false
                    provider: null
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /users/password:
    post:
      operationId: requestPasswordReset
      tags:
        - Auth
      security: []
      summary: Request password reset instructions
      description: Sends a password reset email to the given address. In paranoid mode (default), always returns success — even if the email is not registered — to prevent email enumeration. Blank email returns 422.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - user
              properties:
                user:
                  type: object
                  required:
                    - email
                  properties:
                    email:
                      type: string
                      format: email
                      description: Email address of the account to reset.
                      example: jane@example.com
            examples:
              reset_request:
                summary: Request password reset
                value:
                  user:
                    email: jane@example.com
      responses:
        '200':
          description: Instructions sent (or paranoid success)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                sent:
                  summary: Reset instructions sent
                  value:
                    toast:
                      title: Instructions sent.
                      message:
                        - If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes.
                      variant: success
        '422':
          description: Validation error (blank email)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                blank_email:
                  summary: Email is blank
                  value:
                    toast:
                      title: Could not send instructions.
                      message:
                        - Email can't be blank
                      variant: danger
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    put:
      operationId: executePasswordReset
      tags:
        - Auth
      security: []
      summary: Reset password using token from email
      description: Resets the user's password using the token from the reset email. On success, signs the user in and returns a success toast. On failure (invalid token, mismatched passwords, complexity violation), returns 422 with error details.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - user
              properties:
                user:
                  type: object
                  required:
                    - reset_password_token
                    - password
                    - password_confirmation
                  properties:
                    reset_password_token:
                      type: string
                      description: Token from the password reset email link.
                      example: abc123def456
                    password:
                      type: string
                      format: password
                      description: New password (must meet complexity requirements).
                      example: N3wS3cure!#Pass
                    password_confirmation:
                      type: string
                      format: password
                      description: Must match the new password.
                      example: N3wS3cure!#Pass
            examples:
              reset:
                summary: Reset with valid token
                value:
                  user:
                    reset_password_token: abc123def456
                    password: N3wS3cure!#Pass
                    password_confirmation: N3wS3cure!#Pass
      responses:
        '200':
          description: Password reset successfully — user signed in
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                reset:
                  summary: Successful password reset
                  value:
                    toast:
                      title: Password reset.
                      message:
                        - Your password has been changed successfully. You are now signed in.
                      variant: success
        '422':
          description: Invalid token, expired token, or password validation failure
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                invalid_token:
                  summary: Bad reset token
                  value:
                    toast:
                      title: Could not reset password.
                      message:
                        - Reset password token is invalid
                      variant: danger
                complexity:
                  summary: Password complexity failure
                  value:
                    toast:
                      title: Could not reset password.
                      message:
                        - Password must include at least 2 numbers
                      variant: danger
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /users/password/edit:
    get:
      operationId: validateResetToken
      tags:
        - Auth
      security: []
      summary: Validate a password reset token
      description: Checks whether a password reset token is valid and not expired. The SPA calls this when the user follows the reset link to determine whether to show the reset form or an error message. Returns the minimum password length for client-side validation.
      parameters:
        - name: reset_password_token
          in: query
          required: true
          description: The raw reset token from the email link.
          schema:
            type: string
          example: abc123def456
      responses:
        '200':
          description: Token is valid
          content:
            application/json:
              schema:
                type: object
                required:
                  - valid
                  - minimum_password_length
                properties:
                  valid:
                    type: boolean
                    description: Whether the token is valid and not expired.
                    example: true
                  minimum_password_length:
                    type: integer
                    description: Minimum password length for client-side validation.
                    example: 15
              examples:
                valid:
                  summary: Valid token
                  value:
                    valid: true
                    minimum_password_length: 15
        '422':
          description: Token is invalid, expired, or missing
          content:
            application/json:
              schema:
                type: object
                required:
                  - valid
                  - error
                properties:
                  valid:
                    type: boolean
                    description: Always false for error responses.
                    example: false
                  error:
                    type: string
                    description: Human-readable error message.
                    example: Reset token is invalid or has expired.
              examples:
                invalid:
                  summary: Invalid token
                  value:
                    valid: false
                    error: Reset token is invalid or has expired.
                missing:
                  summary: No token provided
                  value:
                    valid: false
                    error: No reset token provided.
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /users/confirmation:
    post:
      operationId: resendConfirmation
      tags:
        - Auth
      security: []
      summary: Resend email confirmation instructions
      description: Sends a new confirmation email to the given address. In paranoid mode (default), always returns success — even if the email is not registered or already confirmed — to prevent email enumeration. Blank email returns 422.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - user
              properties:
                user:
                  type: object
                  required:
                    - email
                  properties:
                    email:
                      type: string
                      format: email
                      description: Email address to send confirmation to.
                      example: jane@example.com
            examples:
              resend:
                summary: Resend confirmation
                value:
                  user:
                    email: jane@example.com
      responses:
        '200':
          description: Instructions sent (or paranoid success)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                sent:
                  summary: Confirmation instructions sent
                  value:
                    toast:
                      title: Instructions sent.
                      message:
                        - If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes.
                      variant: success
        '422':
          description: Validation error (blank email)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                blank_email:
                  summary: Email is blank
                  value:
                    toast:
                      title: Could not send instructions.
                      message:
                        - Email can't be blank
                      variant: danger
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /users/unlock:
    post:
      operationId: requestUnlockInstructions
      tags:
        - Auth
      security: []
      summary: Request account unlock instructions
      description: Sends unlock instructions email to the given address. In paranoid mode (default), always returns success — even if the email is not registered or the account is not locked — to prevent email enumeration. Blank email returns 422. Requires lockout to be enabled with an email-based unlock strategy (both or email).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - user
              properties:
                user:
                  type: object
                  required:
                    - email
                  properties:
                    email:
                      type: string
                      format: email
                      description: Email address of the locked account.
                      example: jane@example.com
            examples:
              unlock_request:
                summary: Request unlock
                value:
                  user:
                    email: jane@example.com
      responses:
        '200':
          description: Instructions sent (or paranoid success)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                sent:
                  summary: Unlock instructions sent
                  value:
                    toast:
                      title: Instructions sent.
                      message:
                        - If your email address exists in our database, you will receive an email with instructions for how to unlock your account in a few minutes.
                      variant: success
        '422':
          description: Validation error (blank email)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                blank_email:
                  summary: Email is blank
                  value:
                    toast:
                      title: Could not send instructions.
                      message:
                        - Email can't be blank
                      variant: danger
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /consent/acknowledge:
    post:
      operationId: acknowledgeConsent
      tags:
        - System
      summary: Acknowledge consent banner (AC-8)
      description: Records the user's acknowledgment of the system consent banner in the Rails session. Required by NIST AC-8 before interacting with the application. Does not require authentication — the consent modal appears before login.
      security: []
      responses:
        '200':
          description: Consent acknowledged (timestamp stored in session)
  /projects/{projectId}/project_access_requests:
    post:
      operationId: createProjectAccessRequest
      tags:
        - Projects
      summary: Request access to a project
      description: Creates an access request for the current user on the specified project. Project admins are notified via email (if SMTP is enabled). Returns JSON with toast and the new request ID, or an HTML redirect for browser requests. Returns 422 if the user has already requested access.
      parameters:
        - name: projectId
          in: path
          required: true
          description: Numeric ID of the project to request access to.
          schema:
            type: integer
          example: 7
      responses:
        '200':
          description: Access request created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccessRequestToastResponse'
              examples:
                submitted:
                  summary: Access request submitted
                  value:
                    toast:
                      title: Access request submitted.
                      message:
                        - Your request for access has been sent.
                      variant: success
                    id: 42
        '422':
          description: Validation error — the user has already requested access
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
              examples:
                duplicate_request:
                  summary: Duplicate access request
                  value:
                    toast:
                      title: Could not request access.
                      message:
                        - User has already requested access to this project
                      variant: danger
  /projects/{projectId}/project_access_requests/{requestId}:
    delete:
      operationId: destroyProjectAccessRequest
      tags:
        - Projects
      summary: Deny or cancel a project access request
      description: Admins can deny a pending access request; the requesting user can cancel their own request. If SMTP is enabled, a rejection email is sent when an admin denies. Returns JSON with toast and destroyed request ID, or HTML redirect for browser requests.
      parameters:
        - name: projectId
          in: path
          required: true
          description: Numeric ID of the project.
          schema:
            type: integer
          example: 7
        - name: requestId
          in: path
          required: true
          description: Numeric ID of the access request.
          schema:
            type: integer
          example: 42
      responses:
        '200':
          description: Access request destroyed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccessRequestToastResponse'
              examples:
                denied:
                  summary: Admin denied access request
                  value:
                    toast:
                      title: Access request denied.
                      message:
                        - Successfully denied Jane Doe's request to access project.
                      variant: success
                    id: 42
                cancelled:
                  summary: Requester cancelled their own request
                  value:
                    toast:
                      title: Access request cancelled.
                      message:
                        - Your request to access Photon OS 5 has been cancelled.
                      variant: success
                    id: 42
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Request could not be destroyed.
  /projects/{projectId}/triage_response_templates:
    get:
      operationId: listTriageResponseTemplates
      tags:
        - Projects
      summary: List triage response templates for a project
      description: Returns all response templates for the specified project, ordered by name. Viewer+ role required. Templates are reusable canned responses that triagers can insert into the response textarea when making triage decisions.
      parameters:
        - name: projectId
          in: path
          required: true
          description: Numeric ID of the project.
          schema:
            type: integer
          example: 42
      responses:
        '200':
          description: List of templates
          content:
            application/json:
              schema:
                type: object
                required:
                  - triage_response_templates
                properties:
                  triage_response_templates:
                    type: array
                    items:
                      $ref: '#/components/schemas/TriageResponseTemplate'
              examples:
                with_templates:
                  summary: Project with templates
                  value:
                    triage_response_templates:
                      - id: 1
                        name: Accept - standard
                        body: Concur with the finding as written.
                        created_by_id: 42
                        created_at: '2026-06-01T10:00:00Z'
                empty:
                  summary: No templates yet
                  value:
                    triage_response_templates: []
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    post:
      operationId: createTriageResponseTemplate
      tags:
        - Projects
      summary: Create a triage response template
      description: Creates a new reusable response template for the project. Admin role required. Template names must be unique within the project (case-insensitive).
      parameters:
        - name: projectId
          in: path
          required: true
          description: Numeric ID of the project.
          schema:
            type: integer
          example: 42
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - triage_response_template
              properties:
                triage_response_template:
                  type: object
                  required:
                    - name
                    - body
                  properties:
                    name:
                      type: string
                      description: Short display name (max 200 chars).
                      example: Decline - needs evidence
                    body:
                      type: string
                      description: Markdown response text.
                      example: Unable to incorporate without supporting evidence.
            examples:
              basic:
                summary: Create a template
                value:
                  triage_response_template:
                    name: Decline - needs evidence
                    body: Unable to incorporate without supporting evidence.
      responses:
        '201':
          description: Template created
          content:
            application/json:
              schema:
                type: object
                properties:
                  triage_response_template:
                    $ref: '#/components/schemas/TriageResponseTemplate'
              examples:
                created:
                  summary: Newly created template
                  value:
                    triage_response_template:
                      id: 3
                      name: Decline - needs evidence
                      body: Unable to incorporate without supporting evidence.
                      created_by_id: 42
                      created_at: '2026-06-03T01:00:00Z'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /projects/{projectId}/triage_response_templates/{id}:
    patch:
      operationId: updateTriageResponseTemplate
      tags:
        - Projects
      summary: Update a triage response template
      description: Updates an existing response template. Admin role required. Name uniqueness is enforced within the project.
      parameters:
        - name: projectId
          in: path
          required: true
          description: Numeric ID of the project.
          schema:
            type: integer
          example: 42
        - name: id
          in: path
          required: true
          description: Numeric ID of the template.
          schema:
            type: integer
          example: 1
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - triage_response_template
              properties:
                triage_response_template:
                  type: object
                  properties:
                    name:
                      type: string
                      example: Accept - updated
                    body:
                      type: string
                      example: Updated response text.
      responses:
        '200':
          description: Template updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  triage_response_template:
                    $ref: '#/components/schemas/TriageResponseTemplate'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    put:
      operationId: replaceTriageResponseTemplate
      tags:
        - Projects
      summary: Replace a triage response template
      description: Full replacement of an existing template. Same behavior as PATCH. Admin role required.
      parameters:
        - name: projectId
          in: path
          required: true
          description: Numeric ID of the project.
          schema:
            type: integer
          example: 42
        - name: id
          in: path
          required: true
          description: Numeric ID of the template.
          schema:
            type: integer
          example: 1
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - triage_response_template
              properties:
                triage_response_template:
                  type: object
                  required:
                    - name
                    - body
                  properties:
                    name:
                      type: string
                      example: Accept - standard
                    body:
                      type: string
                      example: Concur with the finding as written.
      responses:
        '200':
          description: Template replaced
          content:
            application/json:
              schema:
                type: object
                properties:
                  triage_response_template:
                    $ref: '#/components/schemas/TriageResponseTemplate'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    delete:
      operationId: deleteTriageResponseTemplate
      tags:
        - Projects
      summary: Delete a triage response template
      description: Permanently deletes a response template. Admin role required. Returns 204 No Content on success.
      parameters:
        - name: projectId
          in: path
          required: true
          description: Numeric ID of the project.
          schema:
            type: integer
          example: 42
        - name: id
          in: path
          required: true
          description: Numeric ID of the template.
          schema:
            type: integer
          example: 1
      responses:
        '204':
          description: Template deleted
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /personal_access_tokens:
    get:
      operationId: listPersonalAccessTokens
      tags:
        - Personal Access Tokens
      summary: List current user's API tokens
      description: Returns all personal access tokens for the authenticated user, ordered by creation date (newest first). Admins can pass user_id to list another user's tokens. Token digests are never exposed. Session auth only — token-authenticated requests receive 403.
      security:
        - cookieAuth: []
      parameters:
        - name: user_id
          in: query
          required: false
          description: Admin-only. List tokens for this user instead of the current user.
          schema:
            type: integer
          example: 42
      responses:
        '200':
          description: Token list
          content:
            application/json:
              schema:
                type: object
                required:
                  - personal_access_tokens
                additionalProperties: false
                properties:
                  personal_access_tokens:
                    type: array
                    items:
                      description: 'Shaped by the caller: the owner''s own tokens, or — when an administrator passes user_id — that user''s tokens with the owner identity attached.'
                      oneOf:
                        - $ref: '#/components/schemas/PersonalAccessTokenSummary'
                        - $ref: '#/components/schemas/PersonalAccessTokenAdminSummary'
              examples:
                with_tokens:
                  summary: User has two active tokens
                  value:
                    personal_access_tokens:
                      - id: 1
                        name: CI Pipeline
                        token_prefix: vulcan_a
                        scopes:
                          - read
                          - write
                        expires_at: '2026-08-30'
                        last_used_at: 2026-05-30 14:22:01 UTC
                        revoked_at: null
                        allowed_ips: null
                        created_at: 2026-05-30 10:00:00 UTC
        '403':
          description: Token-authenticated request rejected — token management requires a session (RFC 9457 problem details)
          content:
            application/problem+json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - type
                  - title
                  - status
                  - detail
                properties:
                  type:
                    type: string
                    description: Stable machine identifier for the error class.
                    example: /docs/api/errors#session_authentication_required
                  title:
                    type: string
                    description: Short human summary of the error class.
                    example: Session authentication required
                  status:
                    type: integer
                    description: HTTP status code, repeated in the body.
                    example: 403
                  detail:
                    type: string
                    description: Occurrence-specific human explanation.
                    example: Token management requires a signed-in browser session; API tokens cannot create or revoke tokens.
              examples:
                session_required:
                  summary: API token used against token management
                  value:
                    type: /docs/api/errors#session_authentication_required
                    title: Session authentication required
                    status: 403
                    detail: Token management requires a signed-in browser session; API tokens cannot create or revoke tokens.
        '404':
          $ref: '#/components/responses/NotFound'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
    post:
      operationId: createPersonalAccessToken
      tags:
        - Personal Access Tokens
      summary: Create a new API token
      description: 'Creates a personal access token ON THE SIGNED-IN ACCOUNT. The raw token is returned ONCE in the response — it is never stored or retrievable after this. Requires current password for session hijack protection. Session auth only. Max 20 active tokens per user, max 365-day lifetime. Ownership can never be redirected: a token authenticates AS its owner, so no caller — administrators included — may mint one on another user''s account, and any user_id supplied in the body is ignored. Administrators oversee other users'' tokens by listing (GET with user_id) and revoking them; account recovery goes through a password reset, where the user re-authenticates.'
      security:
        - cookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - personal_access_token
              properties:
                personal_access_token:
                  type: object
                  required:
                    - name
                    - scopes
                    - current_password
                  properties:
                    name:
                      type: string
                      example: CI Pipeline
                    scopes:
                      type: array
                      items:
                        type: string
                        enum:
                          - read
                          - write
                          - admin
                      example:
                        - read
                        - write
                    expires_at:
                      type: string
                      format: date
                      example: '2026-08-30'
                    allowed_ips:
                      type: array
                      items:
                        type: string
                      example:
                        - 10.0.0.0/8
                    current_password:
                      type: string
                      description: Required for session hijack protection.
      responses:
        '201':
          description: Token created — raw token shown once
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PersonalAccessTokenCreateResponse'
          links:
            RevokeCreatedToken:
              operationId: revokePersonalAccessToken
              parameters:
                tokenId: $response.body#/personal_access_token/id
              description: Revoke the newly created token.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          description: Validation failed. Toast JSON for domain validation (invalid scopes, max tokens reached, etc.); RFC 9457 problem details when the current password re-verification fails — token creation re-verifies the signed-in user's password before minting a credential, and 422 (not 401) says the authenticated caller's input was wrong.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
            application/problem+json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - type
                  - title
                  - status
                  - detail
                properties:
                  type:
                    type: string
                    description: Stable machine identifier for the error class.
                    example: /docs/api/errors#incorrect_password
                  title:
                    type: string
                    description: Short human summary of the error class.
                    example: Incorrect password
                  status:
                    type: integer
                    description: HTTP status code, repeated in the body.
                    example: 422
                  detail:
                    type: string
                    description: Occurrence-specific human explanation.
                    example: Creating or managing API tokens re-verifies your identity, and the current password provided does not match.
              examples:
                incorrect_password:
                  summary: Password re-verification failed
                  value:
                    type: /docs/api/errors#incorrect_password
                    title: Incorrect password
                    status: 422
                    detail: Creating or managing API tokens re-verifies your identity, and the current password provided does not match.
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /personal_access_tokens/{tokenId}:
    parameters:
      - name: tokenId
        in: path
        required: true
        description: Numeric ID of the personal access token.
        schema:
          type: integer
        example: 1
    delete:
      operationId: revokePersonalAccessToken
      tags:
        - Personal Access Tokens
      summary: Revoke an API token
      description: Soft-deletes the token by setting revoked_at. The token immediately stops working for API authentication. Audit trail is preserved. Session auth only — users can only revoke their own tokens.
      security:
        - cookieAuth: []
      responses:
        '200':
          description: Token revoked
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
        '404':
          $ref: '#/components/responses/NotFound'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
  /personal_access_tokens/{tokenId}/admin_revoke:
    parameters:
      - name: tokenId
        in: path
        required: true
        description: Numeric ID of the personal access token to admin-revoke.
        schema:
          type: integer
        example: 1
    delete:
      operationId: adminRevokePersonalAccessToken
      tags:
        - Personal Access Tokens
      summary: Admin revoke any user's token
      description: Admin-only endpoint. Revokes any user's token with a required audit comment explaining the reason (e.g. compromised credentials). The audit comment is recorded in the audit trail. Session auth only.
      security:
        - cookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - audit_comment
              properties:
                audit_comment:
                  type: string
                  description: Reason for admin revocation (recorded in audit trail).
                  example: Compromised credentials reported by user.
      responses:
        '200':
          description: Token revoked by admin
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToastResponse'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        4XX:
          $ref: '#/components/responses/ErrorResponse'
components:
  securitySchemes:
    cookieAuth:
      type: apiKey
      in: cookie
      name: _vulcan_session
    tokenAuth:
      type: http
      scheme: token
      description: 'Personal access token authentication. Send via Authorization header: `Authorization: Token vulcan_xxx`. Tokens are SHA-256 hashed server-side (never stored in plaintext). Scopes: read (GET), write (mutations), admin (everything). Create tokens via Settings → API Tokens in the web UI.'
  schemas:
    CurrentUserResponse:
      description: Authenticated user identity for SPA route guards and UI state.
      type: object
      required:
        - id
        - email
        - admin
      properties:
        id:
          type: integer
          description: Unique user identifier.
          example: 42
        name:
          type:
            - string
            - 'null'
          description: Display name. Null for users who haven't set one.
          example: Jane Doe
        email:
          type: string
          format: email
          description: Login email address.
          example: jane@example.com
        admin:
          type: boolean
          description: Whether the user has admin privileges.
          example: false
        provider:
          type:
            - string
            - 'null'
          description: Authentication provider (local, github, ldap, oidc). Null for local.
          example: null
    NavigationResponse:
      description: App shell navigation data for SPA navbar. Includes static nav links, pending access request notifications, and locked user alerts.
      type: object
      required:
        - nav_links
        - access_requests
        - locked_users
      properties:
        nav_links:
          type: array
          description: Primary navigation links for the sidebar/navbar. Every entry is a top-level link; the documentation entry points at the docs site served by the application.
          items:
            type: object
            properties:
              icon:
                type: string
                description: Bootstrap icon name.
                example: folder2-open
              name:
                type: string
                description: Display label.
                example: Projects
              link:
                type: string
                description: URL path.
                example: /projects
        access_requests:
          type: array
          description: Pending project access requests visible to the current user. Empty for non-admin users.
          items:
            type: object
            properties:
              id:
                type: integer
                description: Access request ID.
                example: 1
              user:
                type: object
                description: Requesting user summary.
                properties:
                  id:
                    type: integer
                    example: 42
                  name:
                    type: string
                    example: Jane Doe
                  email:
                    type: string
                    format: email
                    example: jane@example.com
              project:
                type: object
                description: Target project summary.
                properties:
                  id:
                    type: integer
                    example: 7
                  name:
                    type: string
                    example: RHEL 9 STIG
        locked_users:
          type: array
          description: Locked user accounts (admin-only, lockout enabled). Empty for non-admin or when lockout disabled.
          items:
            type: object
            properties:
              id:
                type: integer
                example: 42
              name:
                type: string
                example: Locked User
              email:
                type: string
                format: email
                example: locked@example.com
    SettingsResponse:
      description: Public application settings for pre-auth SPA UI.
      type: object
      required:
        - banner
        - consent
        - local_login
        - user_registration
        - ldap
        - oidc
        - smtp
        - password
        - lockout
      properties:
        banner:
          type: object
          description: Classification banner configuration.
          properties:
            enabled:
              type: boolean
              example: true
            text:
              type: string
              example: UNCLASSIFIED
            background_color:
              type: string
              example: '#007a33'
            text_color:
              type: string
              example: '#ffffff'
        consent:
          type: object
          description: Consent/terms-of-use modal configuration.
          properties:
            enabled:
              type: boolean
              example: false
            version:
              type: integer
              description: Consent version number. Incrementing re-prompts all users.
              example: 1
            title:
              type: string
              example: Terms of Use
            content:
              type: string
              example: ''
            ttl:
              type: integer
              description: Time-to-live in seconds. 0 means consent never expires.
              example: 0
        local_login:
          type: object
          description: Local email/password login availability.
          properties:
            enabled:
              type: boolean
              example: true
        user_registration:
          type: object
          description: Self-service registration availability.
          properties:
            enabled:
              type: boolean
              example: true
        ldap:
          type: object
          description: LDAP authentication provider.
          properties:
            enabled:
              type: boolean
              example: false
            title:
              type:
                - string
                - 'null'
              description: Display name for the LDAP login button.
              example: null
        oidc:
          type: object
          description: OpenID Connect authentication provider.
          properties:
            enabled:
              type: boolean
              example: false
            title:
              type:
                - string
                - 'null'
              description: Display name for the OIDC login button.
              example: null
        smtp:
          type: object
          description: Whether outbound email is available.
          properties:
            enabled:
              type: boolean
              example: false
        password:
          type: object
          description: Password complexity policy for client-side validation.
          properties:
            min_length:
              type: integer
              example: 15
            min_uppercase:
              type: integer
              example: 2
            min_lowercase:
              type: integer
              example: 2
            min_number:
              type: integer
              example: 2
            min_special:
              type: integer
              example: 2
        lockout:
          type: object
          description: Account lockout policy.
          properties:
            enabled:
              type: boolean
              example: true
            maximum_attempts:
              type: integer
              example: 3
            last_attempt_warning:
              type: boolean
              example: true
    VersionResponse:
      description: Application version and runtime information. No authentication required.
      type: object
      required:
        - name
        - version
        - rails
        - ruby
        - environment
      additionalProperties: false
      properties:
        name:
          type: string
          description: Application name.
          example: Vulcan
        version:
          type: string
          description: Current Vulcan application version.
          example: 2.4.1
        rails:
          type: string
          description: Rails framework version running the application.
          example: 8.1.3.1
        ruby:
          type: string
          description: Ruby interpreter version running the application.
          example: 3.4.10
        environment:
          type: string
          description: Current Rails environment.
          enum:
            - development
            - test
            - production
          example: development
    GlobalSearchResponse:
      description: Results from a global search across projects, components, rules, SRGs, STIGs, and their rules. Serialized by Api::SearchController#global. Each sub-array uses controller-specific inline hashes, NOT Blueprints.
      type: object
      required:
        - projects
        - components
        - rules
        - srgs
        - stigs
        - stig_rules
        - srg_rules
      properties:
        projects:
          type: array
          description: Projects matching the search query (from user's available_projects).
          items:
            type: object
            properties:
              id:
                type: integer
                description: Unique project identifier.
                example: 1
              name:
                type: string
                description: Project name.
                example: Photon 3
              description:
                type:
                  - string
                  - 'null'
                description: Project description.
                example: null
              components_count:
                type: integer
                description: Number of components in the project.
                example: 1
        components:
          type: array
          description: Components matching the search query, scoped to what the caller can access — project or component membership, or released components; admins unrestricted.
          items:
            type: object
            properties:
              id:
                type: integer
                description: Unique component identifier.
                example: 1
              name:
                type: string
                description: Component name.
                example: Photon OS 3
              version:
                type:
                  - integer
                  - 'null'
                description: Component version number.
                example: 1
              release:
                type:
                  - integer
                  - 'null'
                description: Component release number.
                example: 1
              project_id:
                type: integer
                description: ID of the parent project.
                example: 1
              project_name:
                type:
                  - string
                  - 'null'
                description: Name of the parent project.
                example: Photon 3
              metadata:
                type:
                  - object
                  - 'null'
                description: Component metadata key-value pairs (from component_metadata.data).
                example: null
        rules:
          type: array
          description: 'Requirement rows of both document kinds — stig rules and authored SRG requirements — matching the search query, scoped to what the caller can access: project or component membership, or released components; admins unrestricted.'
          items:
            type: object
            properties:
              id:
                type: integer
                description: Unique rule identifier.
                example: 1786
              rule_id:
                type: string
                description: Six-digit zero-padded rule number.
                example: '000001'
              title:
                type:
                  - string
                  - 'null'
                description: Title of the security control.
                example: The operating system must provide automated mechanisms for supporting account management functions.
              status:
                type:
                  - string
                  - 'null'
                description: Current applicability status of the rule.
                example: Not Yet Determined
              component_id:
                type: integer
                description: ID of the component this rule belongs to.
                example: 1
              component_prefix:
                type:
                  - string
                  - 'null'
                description: Prefix of the parent component.
                example: PHOS-03
              snippet:
                type:
                  - string
                  - 'null'
                description: Text excerpt from the matched field showing the search hit in context.
                example: '[Vuln discussion] ...provide automated mechanisms for supporting...'
              matched_field:
                type:
                  - string
                  - 'null'
                description: Name of the field where the search term was found.
                example: title
              comment_count:
                type: integer
                description: Number of comments on this rule.
                example: 0
              parent_rule_id:
                type:
                  - integer
                  - 'null'
                description: ID of the parent rule (via satisfies relationship). Null if no parent; always null for authored SRG requirements (satisfies is stig-only).
                example: null
              parent_display_name:
                type:
                  - string
                  - 'null'
                description: Display name of the parent rule. Null if no parent; always null for authored SRG requirements.
                example: null
        srgs:
          type: array
          description: Security Requirements Guides matching the search query.
          items:
            type: object
            properties:
              id:
                type: integer
                description: Unique SRG identifier.
                example: 1
              srg_id:
                type: string
                description: DISA SRG identifier.
                example: Container_Platform_SRG
              name:
                type:
                  - string
                  - 'null'
                description: SRG name.
                example: Container Platform SRG - Ver 2, Rel 4
              title:
                type:
                  - string
                  - 'null'
                description: Full SRG title.
                example: Container Platform Security Requirements Guide
              version:
                type:
                  - string
                  - 'null'
                description: Version string.
                example: V2R4
        stigs:
          type: array
          description: Published STIGs matching the search query.
          items:
            type: object
            properties:
              id:
                type: integer
                description: Unique STIG identifier.
                example: 1
              stig_id:
                type:
                  - string
                  - 'null'
                description: DISA STIG identifier.
                example: Application_Security_Development_STIG
              name:
                type:
                  - string
                  - 'null'
                description: STIG name.
                example: Application Security Development STIG - Ver 6, Rel 4
              title:
                type:
                  - string
                  - 'null'
                description: Full STIG title.
                example: Application Security and Development Security Technical Implementation Guide
              version:
                type:
                  - string
                  - 'null'
                description: Version string.
                example: V6R4
              description:
                type:
                  - string
                  - 'null'
                description: STIG description.
                example: null
        stig_rules:
          type: array
          description: Individual rules from published STIGs matching the search query.
          items:
            type: object
            properties:
              id:
                type: integer
                description: Unique STIG rule identifier.
                example: 600
              rule_id:
                type: string
                description: DISA rule identifier.
                example: SV-222396r857506_rule
              vuln_id:
                type:
                  - string
                  - 'null'
                description: Vulnerability identifier (V-number).
                example: V-222396
              title:
                type: string
                description: Requirement title.
                example: The application must enforce approved authorizations.
              fixtext:
                type:
                  - string
                  - 'null'
                description: Fix text.
                example: Configure the application to enforce approved authorizations.
              ident:
                type:
                  - string
                  - 'null'
                description: CCI identifiers.
                example: CCI-000213
              stig_id:
                type: integer
                description: ID of the parent STIG.
                example: 1
              stig_name:
                type:
                  - string
                  - 'null'
                description: Name of the parent STIG.
                example: Application Security Development STIG - Ver 6, Rel 4
        srg_rules:
          type: array
          description: Requirements from the published SRG catalog matching the search query. Component-authored requirements are project content and never appear here.
          items:
            type: object
            properties:
              id:
                type: integer
                description: Unique SRG rule identifier.
                example: 500
              rule_id:
                type: string
                description: DISA rule identifier.
                example: SV-222396r857506_rule
              title:
                type: string
                description: Requirement title.
                example: The application must enforce approved authorizations.
              fixtext:
                type:
                  - string
                  - 'null'
                description: Fix text.
                example: Configure the application to enforce approved authorizations.
              ident:
                type:
                  - string
                  - 'null'
                description: CCI identifiers.
                example: CCI-000213
              srg_id:
                type: integer
                description: ID of the parent SRG.
                example: 1
              srg_name:
                type:
                  - string
                  - 'null'
                description: Name of the parent SRG.
                example: Container Platform SRG - Ver 2, Rel 4
    ProjectSummary:
      description: Base project fields shared by all views. Serialized by ProjectBlueprint (default view). View-specific schemas (ProjectShowResponse, ProjectIndexResponse) extend this with additional fields.
      type: object
      required:
        - id
        - name
      properties:
        id:
          type: integer
          description: Unique project identifier.
          example: 1
        name:
          type: string
          description: Human-readable project name.
          example: Photon 3
        description:
          type:
            - string
            - 'null'
          description: Optional longer description of the project purpose and scope.
          example: null
        visibility:
          type:
            - string
            - 'null'
          description: Project visibility setting controlling discoverability by non-members.
          enum:
            - discoverable
            - hidden
            - null
          example: hidden
        memberships_count:
          type: integer
          description: Number of members in this project.
          example: 14
        admin_name:
          type:
            - string
            - 'null'
          description: Display name of the project's point of contact / admin.
          example: Demo Admin
        admin_email:
          type:
            - string
            - 'null'
          format: email
          description: Email address of the project's point of contact / admin.
          example: admin@example.org
        created_at:
          type: string
          description: Timestamp when the project was created. Format is "YYYY-MM-DD HH:MM:SS UTC" (Blueprinter default).
          example: 2026-05-19 14:07:37 UTC
        updated_at:
          type: string
          description: Timestamp when the project was last modified. Format is "YYYY-MM-DD HH:MM:SS UTC" (Blueprinter default).
          example: 2026-05-19 14:08:17 UTC
    PaginationMeta:
      description: Pagination metadata for paginated list endpoints (pagy-backed). Shared by every endpoint returning the canonical { rows, pagination } envelope.
      type: object
      required:
        - page
        - per_page
        - total
      additionalProperties: false
      properties:
        page:
          type: integer
          description: Current page number (1-based).
          example: 1
        per_page:
          type: integer
          description: Records per page (default 25, maximum 100).
          example: 25
        total:
          type: integer
          description: Total number of records across all pages.
          example: 47
    BenchmarkLatestResponse:
      description: Latest-version benchmark listing for dropdown population. One row per benchmark, ranked by numeric V{major}R{minor} version (V10R1 above V4R4). SRG rows carry srg_id; STIG rows carry stig_id.
      type: object
      required:
        - rows
      additionalProperties: false
      properties:
        rows:
          type: array
          description: One entry per benchmark, ordered by title.
          items:
            type: object
            required:
              - id
              - title
              - version
              - name
            additionalProperties: false
            properties:
              id:
                type: integer
                description: Unique benchmark record identifier.
                example: 42
              srg_id:
                type: string
                description: The SRG's stable benchmark identifier across releases. Present only in /api/srgs/latest responses.
                example: General_Purpose_Operating_System
              stig_id:
                type: string
                description: The STIG's stable benchmark identifier across releases. Present only in /api/stigs/latest responses.
                example: RHEL_9_STIG
              title:
                type: string
                description: Full benchmark title.
                example: General Purpose Operating System Security Requirements Guide
              version:
                type: string
                description: DISA version string in V{major}R{minor} format.
                example: V3R3
              name:
                type:
                  - string
                  - 'null'
                description: Human-friendly display name with version and release.
                example: General Purpose Operating System - Ver 3, Rel 3
    BenchmarkStatsResponse:
      description: Aggregate stats for a benchmark. SRG responses include a usage section — which components are based on the SRG, scoped to components the caller can see (member projects or released). STIG responses carry counts only.
      type: object
      required:
        - rule_count
        - severity_counts
      additionalProperties: false
      properties:
        rule_count:
          type: integer
          description: Total number of rules in the benchmark.
          example: 250
        severity_counts:
          type: object
          description: Rule counts grouped by severity (SQL aggregate).
          required:
            - high
            - medium
            - low
          additionalProperties: false
          properties:
            high:
              type: integer
              description: Count of CAT I (high severity) rules.
              example: 30
            medium:
              type: integer
              description: Count of CAT II (medium severity) rules.
              example: 200
            low:
              type: integer
              description: Count of CAT III (low severity) rules.
              example: 20
        usage:
          type: object
          description: Components based on this SRG, scoped to the caller's visibility (member projects or released). Present only on SRG stats responses.
          required:
            - count
            - components
          additionalProperties: false
          properties:
            count:
              type: integer
              description: Number of caller-visible components based on this SRG.
              example: 2
            components:
              type: array
              description: Caller-visible components, ordered by prefix.
              items:
                type: object
                required:
                  - id
                  - name
                  - project_id
                  - project_name
                additionalProperties: false
                properties:
                  id:
                    type: integer
                    description: Component identifier.
                    example: 38
                  name:
                    type: string
                    description: Component name.
                    example: RHEL 9 Hardened Baseline
                  project_id:
                    type: integer
                    description: Owning project identifier.
                    example: 7
                  project_name:
                    type: string
                    description: Owning project name.
                    example: Photon OS 5 Hardening
    ComponentLatestResponse:
      description: Latest released component per prefix for dropdown population. A component is a STIG in progress — released ones are instance-wide reference data. Ranking is numeric on the integer version/release pair; unreleased drafts never appear.
      type: object
      required:
        - rows
      additionalProperties: false
      properties:
        rows:
          type: array
          description: One entry per prefix, ordered by prefix.
          items:
            type: object
            required:
              - id
              - prefix
              - name
            additionalProperties: false
            properties:
              id:
                type: integer
                description: Unique component identifier.
                example: 38
              prefix:
                type: string
                description: STIG ID prefix — the component's identity across releases.
                example: RHEL-09
              name:
                type: string
                description: Component name.
                example: RHEL 9 Hardened Baseline
              title:
                type:
                  - string
                  - 'null'
                description: Component title. Null when not set.
                example: Red Hat Enterprise Linux 9
              version:
                type:
                  - integer
                  - 'null'
                description: Component version number.
                example: 2
              release:
                type:
                  - integer
                  - 'null'
                description: Component release number within the version.
                example: 1
    ComponentSummaryResponse:
      description: 'Lightweight component header for SPA triage/settings routes: identity, counts, SRG info, the caller''s effective permissions, and the serialized comment-phase state machine. Never includes the rules/reviews/histories arrays — fetch the full component for those. The phase booleans mirror the server''s write-guards, so clients can predict rejections without reimplementing phase logic.'
      type: object
      required:
        - id
        - name
        - prefix
        - document_type
        - title
        - released
        - project_id
        - rules_count
        - severity_counts
        - comment_phase
        - accepting_new_comments
        - triaging_active
        - frozen_for_writes
      additionalProperties: false
      properties:
        id:
          type: integer
          description: Unique component identifier.
          example: 38
        name:
          type: string
          description: Component name.
          example: RHEL 9 Hardened Baseline
        prefix:
          type: string
          description: STIG ID prefix.
          example: RHEL-09
        document_type:
          type: string
          description: Authoring profile of the component (stig or srg). Immutable after creation.
          enum:
            - stig
            - srg
          example: stig
        title:
          type: string
          description: Component title.
          example: Red Hat Enterprise Linux 9
        version:
          type:
            - integer
            - 'null'
          description: Component version number.
          example: 2
        release:
          type:
            - integer
            - 'null'
          description: Component release number within the version.
          example: 1
        released:
          type: boolean
          description: Whether the component has been released.
          example: false
        project_id:
          type: integer
          description: Owning project identifier.
          example: 7
        component_id:
          type:
            - integer
            - 'null'
          description: Parent component identifier when this is an overlay. Null otherwise.
          example: null
        security_requirements_guide_id:
          type: integer
          description: Identifier of the SRG this component is based on.
          example: 3
        based_on_title:
          type: string
          description: Title of the SRG this component is based on.
          example: General Purpose Operating System Security Requirements Guide
        based_on_version:
          type: string
          description: Version of the SRG this component is based on.
          example: V3R3
        rules_count:
          type: integer
          description: Number of requirement rows in the component — rules for stig-kind components, authored requirements for srg-kind components.
          example: 203
        memberships_count:
          type: integer
          description: Number of direct component memberships.
          example: 4
        severity_counts:
          type: object
          description: Rule counts grouped by severity.
          required:
            - high
            - medium
            - low
          additionalProperties: false
          properties:
            high:
              type: integer
              description: Count of CAT I (high severity) rules.
              example: 20
            medium:
              type: integer
              description: Count of CAT II (medium severity) rules.
              example: 173
            low:
              type: integer
              description: Count of CAT III (low severity) rules.
              example: 10
        pending_comment_count:
          type: integer
          description: Number of pending top-level comments awaiting triage.
          example: 5
        effective_permissions:
          type:
            - string
            - 'null'
          description: The caller's effective role on this component (admin, author, reviewer, viewer). Null when the caller has no membership-derived permissions (e.g., reading a released component as a non-member).
          example: viewer
        updated_at:
          type: string
          format: date-time
          description: Last update timestamp.
          example: '2026-07-10T14:07:37.142Z'
        comment_phase:
          type: string
          enum:
            - open
            - closed
          description: Public comment period phase.
          example: open
        closed_reason:
          type:
            - string
            - 'null'
          enum:
            - adjudicating
            - finalized
            - null
          description: Why the comment period is closed. Null while open or when unset.
          example: null
        comment_period_starts_at:
          type:
            - string
            - 'null'
          format: date-time
          description: Comment period start timestamp. Null when not scheduled.
          example: '2026-07-01T00:00:00.000Z'
        comment_period_ends_at:
          type:
            - string
            - 'null'
          format: date-time
          description: Comment period end timestamp. Null when not scheduled.
          example: '2026-07-15T00:00:00.000Z'
        accepting_new_comments:
          type: boolean
          description: Server write-guard — whether new comments are accepted (phase is open).
          example: true
        triaging_active:
          type: boolean
          description: Server write-guard — whether triage continues (open, or closed while adjudicating).
          example: true
        frozen_for_writes:
          type: boolean
          description: Server write-guard — whether the component is read-only (closed and finalized).
          example: false
        comment_period_days_remaining:
          type:
            - integer
            - 'null'
          description: Days until the comment period ends. Null when comments are not being accepted, no end date is set, or the end date has passed.
          example: 5
    RulesByStatus:
      description: Rule counts grouped into the five canonical DISA status buckets (SQL aggregate).
      type: object
      required:
        - not_yet_determined
        - applicable_configurable
        - applicable_inherently_meets
        - applicable_does_not_meet
        - not_applicable
      additionalProperties: false
      properties:
        not_yet_determined:
          type: integer
          description: Rules with status Not Yet Determined.
          example: 50
        applicable_configurable:
          type: integer
          description: Rules with status Applicable - Configurable.
          example: 120
        applicable_inherently_meets:
          type: integer
          description: Rules with status Applicable - Inherently Meets.
          example: 15
        applicable_does_not_meet:
          type: integer
          description: Rules with status Applicable - Does Not Meet.
          example: 8
        not_applicable:
          type: integer
          description: Rules with status Not Applicable.
          example: 10
    SrgRulesByStatus:
      description: Requirement counts for an SRG-kind component, grouped into the three-value SRG authoring vocabulary. Disjoint from the five-bucket STIG shape — the two never collapse into one list.
      type: object
      required:
        - not_yet_determined
        - applicable
        - not_applicable
      additionalProperties: false
      properties:
        not_yet_determined:
          type: integer
          description: Authored requirements with status Not Yet Determined.
          example: 12
        applicable:
          type: integer
          description: Authored requirements with status Applicable.
          example: 180
        not_applicable:
          type: integer
          description: Authored requirements with status Not Applicable.
          example: 3
    SeverityBuckets:
      description: Rule counts grouped by severity (SQL aggregate).
      type: object
      required:
        - high
        - medium
        - low
      additionalProperties: false
      properties:
        high:
          type: integer
          description: Count of CAT I (high severity) rules.
          example: 20
        medium:
          type: integer
          description: Count of CAT II (medium severity) rules.
          example: 173
        low:
          type: integer
          description: Count of CAT III (low severity) rules.
          example: 10
    ComponentStatsResponse:
      description: 'Rule statistics for one component: counts by status and severity plus completion and lock percentages. Percentages are null when the component has no rules — never a fabricated 0.0.'
      type: object
      required:
        - document_type
        - rules_by_status
        - rules_by_severity
        - rule_count
        - completion_pct
        - lock_pct
      additionalProperties: false
      properties:
        document_type:
          type: string
          description: Authoring profile of the component — tells clients which rules_by_status branch to expect.
          enum:
            - stig
            - srg
          example: stig
        rules_by_status:
          description: 'Status buckets shaped by the component''s document_type: the five STIG buckets or the three SRG buckets. The two branch shapes are disjoint; the component''s document_type tells clients which to expect.'
          oneOf:
            - $ref: '#/components/schemas/RulesByStatus'
            - $ref: '#/components/schemas/SrgRulesByStatus'
        rules_by_severity:
          $ref: '#/components/schemas/SeverityBuckets'
        rule_count:
          type: integer
          description: Total number of rules in the component.
          example: 203
        completion_pct:
          type:
            - number
            - 'null'
          description: Percentage of rules with a determined status (anything other than Not Yet Determined), rounded to one decimal. Null when there are no rules.
          example: 75.4
        lock_pct:
          type:
            - number
            - 'null'
          description: Percentage of locked rules, rounded to one decimal. Null when there are no rules.
          example: 12.3
    ComponentWorkflowStateResponse:
      description: Workflow readiness for a component across the authoring, lock, review, comment, triage, and export stages. Counts are SQL aggregates; the comment booleans mirror the server's write-guards.
      type: object
      required:
        - document_type
        - authoring
        - locks
        - reviews
        - comment
        - triage
        - export
      additionalProperties: false
      properties:
        document_type:
          type: string
          description: Authoring profile of the component.
          enum:
            - stig
            - srg
          example: stig
        authoring:
          type: object
          description: Rule authoring progress.
          required:
            - rules_total
            - rules_determined
          additionalProperties: false
          properties:
            rules_total:
              type: integer
              description: Total rules in the component.
              example: 203
            rules_determined:
              type: integer
              description: Rules with a determined status (not Not Yet Determined).
              example: 153
        locks:
          type: object
          description: Rule lock progress toward release.
          required:
            - locked
            - total
            - all_locked
          additionalProperties: false
          properties:
            locked:
              type: integer
              description: Number of locked rules.
              example: 25
            total:
              type: integer
              description: Total rules in the component.
              example: 203
            all_locked:
              type: boolean
              description: True when every rule is locked and at least one rule exists — the precondition for release.
              example: false
        reviews:
          type: object
          description: Open review-request workload.
          required:
            - under_review
          additionalProperties: false
          properties:
            under_review:
              type: integer
              description: Rules currently under review (review requested).
              example: 4
        comment:
          type: object
          description: Public comment period state (server write-guards).
          required:
            - phase
            - accepting_new_comments
            - triaging_active
            - frozen_for_writes
            - pending_comments
          additionalProperties: false
          properties:
            phase:
              type: string
              enum:
                - open
                - closed
              description: Public comment period phase.
              example: open
            accepting_new_comments:
              type: boolean
              description: Whether new comments are accepted (phase is open).
              example: true
            triaging_active:
              type: boolean
              description: Whether triage continues (open, or closed while adjudicating).
              example: true
            frozen_for_writes:
              type: boolean
              description: Whether the component is read-only (closed and finalized).
              example: false
            pending_comments:
              type: integer
              description: Top-level comments awaiting triage — rule-attached and component-attached both count.
              example: 3
        triage:
          type: object
          description: Triage and adjudication workload.
          required:
            - pending
            - awaiting_adjudication
          additionalProperties: false
          properties:
            pending:
              type: integer
              description: Top-level comments awaiting triage.
              example: 3
            awaiting_adjudication:
              type: integer
              description: Comments triaged concur/concur_with_comment/non_concur but not yet adjudicated.
              example: 1
        export:
          type: object
          description: Release/export readiness.
          required:
            - released
            - releasable
          additionalProperties: false
          properties:
            released:
              type: boolean
              description: Whether the component has been released.
              example: false
            releasable:
              type: boolean
              description: Whether the component can be released (all rules locked, not already released).
              example: false
    TriageSummaryResponse:
      description: Triage aggregates over top-level comments — rule-attached and component-attached both count. Served for a single component or aggregated across a project's components. adjudication_pct is null when there are no top-level comments.
      type: object
      required:
        - by_triage_status
        - total
        - adjudicated
        - adjudication_pct
      additionalProperties: false
      properties:
        by_triage_status:
          type: object
          description: Count of top-level comments per triage status (every status key present).
          required:
            - pending
            - concur
            - concur_with_comment
            - non_concur
            - duplicate
            - informational
            - needs_clarification
            - withdrawn
            - addressed_by
          additionalProperties: false
          properties:
            pending:
              type: integer
              description: Comments awaiting triage.
              example: 3
            concur:
              type: integer
              description: Comments triaged as concur.
              example: 1
            concur_with_comment:
              type: integer
              description: Comments triaged as concur with comment.
              example: 0
            non_concur:
              type: integer
              description: Comments triaged as non-concur.
              example: 0
            duplicate:
              type: integer
              description: Comments marked duplicate of another comment.
              example: 0
            informational:
              type: integer
              description: Comments triaged as informational.
              example: 1
            needs_clarification:
              type: integer
              description: Comments needing clarification from the author.
              example: 0
            withdrawn:
              type: integer
              description: Comments withdrawn by their author.
              example: 0
            addressed_by:
              type: integer
              description: Comments addressed by another rule.
              example: 0
        total:
          type: integer
          description: Total top-level comments.
          example: 5
        adjudicated:
          type: integer
          description: Top-level comments with an adjudication timestamp.
          example: 1
        adjudication_pct:
          type:
            - number
            - 'null'
          description: Percentage of top-level comments adjudicated, rounded to one decimal. Null when there are no top-level comments.
          example: 20
    ProjectAggregateStats:
      description: Project-wide requirement statistics aggregated across every component. Per-document-type status sections never collapse into each other; rule_count, completion and lock percentages are type-agnostic.
      type: object
      required:
        - rules_by_status_by_type
        - rules_by_severity
        - rule_count
        - completion_pct
        - lock_pct
      additionalProperties: false
      properties:
        rules_by_status_by_type:
          type: object
          description: Per-document-type status sections.
          required:
            - stig
            - srg
          additionalProperties: false
          properties:
            stig:
              $ref: '#/components/schemas/RulesByStatus'
            srg:
              $ref: '#/components/schemas/SrgRulesByStatus'
        rules_by_severity:
          $ref: '#/components/schemas/SeverityBuckets'
        rule_count:
          type: integer
          description: Total live requirements across all components, every document kind.
          example: 207
        completion_pct:
          type:
            - number
            - 'null'
          description: Percentage of determined requirements (anything other than Not Yet Determined) across all kinds. Null when the project has no requirements.
          example: 75.4
        lock_pct:
          type:
            - number
            - 'null'
          description: Percentage of locked requirements across all kinds. Null when empty.
          example: 12.3
    ProjectStatsResponse:
      description: 'Project-level rule statistics: totals aggregated across every component plus a per-component breakdown. Percentages are null when a denominator is zero.'
      type: object
      required:
        - aggregate
        - components
      additionalProperties: false
      properties:
        aggregate:
          $ref: '#/components/schemas/ProjectAggregateStats'
        components:
          type: array
          description: Per-component breakdown, ordered by prefix.
          items:
            type: object
            required:
              - id
              - name
              - prefix
              - document_type
              - rule_count
              - completion_pct
              - lock_pct
            additionalProperties: false
            properties:
              id:
                type: integer
                description: Component identifier.
                example: 38
              name:
                type: string
                description: Component name.
                example: RHEL 9 Hardened Baseline
              prefix:
                type: string
                description: STIG ID prefix.
                example: RHEL-09
              document_type:
                type: string
                description: Authoring profile of the component — each row carries its own kind.
                enum:
                  - stig
                  - srg
                example: stig
              rule_count:
                type: integer
                description: Rules in this component.
                example: 203
              completion_pct:
                type:
                  - number
                  - 'null'
                description: Percentage of determined rules. Null when the component has no rules.
                example: 75.4
              lock_pct:
                type:
                  - number
                  - 'null'
                description: Percentage of locked rules. Null when the component has no rules.
                example: 12.3
    MembershipSummary:
      description: Project or component membership linking a user to a role.
      type: object
      required:
        - id
        - user_id
        - role
        - membership_type
        - membership_id
      additionalProperties: false
      properties:
        id:
          type: integer
          description: Unique membership identifier.
          example: 15
        user_id:
          type: integer
          description: ID of the member user.
          example: 42
        role:
          type: string
          description: Access level for this membership.
          enum:
            - viewer
            - author
            - reviewer
            - admin
          example: author
        membership_type:
          type: string
          description: Type of resource this membership belongs to.
          enum:
            - Project
            - Component
          example: Project
        membership_id:
          type: integer
          description: ID of the project or component this membership belongs to.
          example: 4
        name:
          type:
            - string
            - 'null'
          description: Display name of the member (delegated from User).
          example: Jane Doe
        email:
          type:
            - string
            - 'null'
          format: email
          description: Email address of the member (delegated from User).
          example: jane.doe@example.org
    ProjectIndexResponse:
      description: Project listing entry with per-user computed fields (admin, is_member, access_request_id).
      type: object
      required:
        - id
        - name
      additionalProperties: false
      properties:
        id:
          type: integer
          description: Unique project identifier.
          example: 4
        name:
          type: string
          description: Project name.
          example: Container Platform
        description:
          type:
            - string
            - 'null'
          description: Project description.
          example: STIG for container orchestration platforms
        visibility:
          type: string
          description: Project visibility level.
          enum:
            - discoverable
            - hidden
          example: discoverable
        memberships_count:
          type: integer
          description: Number of members in this project.
          example: 14
        admin_name:
          type:
            - string
            - 'null'
          description: Name of the project's point of contact.
          example: Demo Admin
        admin_email:
          type:
            - string
            - 'null'
          format: email
          description: Email of the project's point of contact.
          example: admin@example.org
        created_at:
          type: string
          description: When the project was created. Format is "YYYY-MM-DD HH:MM:SS UTC" (Blueprinter default).
          example: 2026-05-19 14:07:37 UTC
        updated_at:
          type: string
          description: When the project was last modified. Format is "YYYY-MM-DD HH:MM:SS UTC" (Blueprinter default).
          example: 2026-05-19 14:08:17 UTC
        memberships:
          type: array
          description: Project members with roles.
          items:
            $ref: '#/components/schemas/MembershipSummary'
        admin:
          type: boolean
          description: Whether the current user is an admin on this project.
          example: true
        is_member:
          type: boolean
          description: Whether the current user is a member of this project.
          example: true
        access_request_id:
          type:
            - integer
            - 'null'
          description: ID of the current user's pending access request, if any.
          example: null
        pending_comment_count:
          type: integer
          description: Number of pending (untriaged) comments across all components.
          example: 10
        total_comment_count:
          type: integer
          description: Total comment count across all components.
          example: 108
        pending_comment_link:
          type:
            - string
            - 'null'
          description: Deep-link to the triage page for pending comments. Null if no comments exist.
          example: /components/29/triage
    ProjectInput:
      description: Input fields for creating or updating a Vulcan project.
      type: object
      required:
        - name
      properties:
        name:
          type: string
          description: Human-readable project name.
          example: Container Platform
        description:
          type: string
          description: Optional longer description of the project purpose and scope.
          example: STIG for container orchestration platforms.
        visibility:
          type: string
          description: Project visibility setting controlling discoverability by non-members.
          enum:
            - discoverable
            - hidden
          example: discoverable
        project_metadata_attributes:
          type: object
          description: Project metadata as a nested-attributes object — the one metadata shape, accepted at creation and update alike. The data hash holds free-form string keys (points of contact, Slack channel, and so on).
          properties:
            data:
              type: object
              description: Free-form metadata keys and values.
              additionalProperties:
                type: string
              example:
                POC Name: Jane Doe
                POC Email: jane@example.com
                Slack Channel ID: C0123456789
    ToastObject:
      description: 'The inner toast notification object with title, message array, and variant. This is the reusable shape — ToastResponse wraps it as { toast: ToastObject }. Use this via $ref when composing multi-key responses (toast + user, toast + reset_url, etc.).'
      type: object
      required:
        - title
        - message
        - variant
      additionalProperties: false
      properties:
        title:
          type: string
          description: Short human-readable summary of the outcome.
          example: User updated.
        message:
          type: array
          description: Detail lines providing additional context about the operation result.
          items:
            type: string
          example:
            - Successfully updated user.
        variant:
          type: string
          description: Bootstrap alert variant controlling how the toast is styled in the UI.
          enum:
            - success
            - danger
            - warning
            - info
          example: success
    ProjectCreateResponse:
      description: Response from project creation. Success includes a toast notification and a redirect URL to the new project page. Error includes only the toast (no redirect_url).
      type: object
      required:
        - toast
      properties:
        toast:
          $ref: '#/components/schemas/ToastObject'
        redirect_url:
          type: string
          description: Relative URL to the newly created project page. Present on success, absent on error.
          example: /projects/1
    ProjectDetails:
      description: Project-wide requirement statistics with per-document-type sections. The stig and srg sections never collapse into each other; top-level numbers (total, lck, nur, ur) are type-agnostic.
      type: object
      required:
        - stig
        - srg
        - nur
        - ur
        - lck
        - total
      additionalProperties: false
      properties:
        stig:
          type: object
          description: STIG-kind requirement counts in the five DISA buckets.
          required:
            - ac
            - aim
            - adnm
            - na
            - nyd
            - total
          additionalProperties: false
          properties:
            ac:
              type: integer
              description: Applicable - Configurable.
              example: 120
            aim:
              type: integer
              description: Applicable - Inherently Meets.
              example: 15
            adnm:
              type: integer
              description: Applicable - Does Not Meet.
              example: 8
            na:
              type: integer
              description: Not Applicable.
              example: 10
            nyd:
              type: integer
              description: Not Yet Determined.
              example: 50
            total:
              type: integer
              description: Total STIG-kind requirements.
              example: 203
        srg:
          type: object
          description: SRG-kind requirement counts in the three-value vocabulary.
          required:
            - applicable
            - na
            - nyd
            - total
          additionalProperties: false
          properties:
            applicable:
              type: integer
              description: Applicable.
              example: 180
            na:
              type: integer
              description: Not Applicable.
              example: 3
            nyd:
              type: integer
              description: Not Yet Determined.
              example: 12
            total:
              type: integer
              description: Total SRG-kind requirements.
              example: 195
        nur:
          type: integer
          description: Unlocked requirements not under review (all kinds).
          example: 40
        ur:
          type: integer
          description: Unlocked requirements under review (all kinds).
          example: 5
        lck:
          type: integer
          description: Locked requirements (all kinds).
          example: 158
        total:
          type: integer
          description: Total live requirements across every document kind.
          example: 398
    ComponentSummary:
      description: Base component fields shared by all views. Serialized by ComponentBlueprint (default view). View-specific schemas (ComponentEditorResponse, ComponentIndexResponse) extend this with additional fields.
      type: object
      required:
        - id
        - name
        - prefix
      properties:
        id:
          type: integer
          description: Unique component identifier.
          example: 1
        name:
          type: string
          description: Human-readable component name.
          example: Photon OS 3
        prefix:
          type: string
          description: Short alphanumeric prefix used to construct rule IDs (e.g. PHOS-03 in PHOS-03-000010).
          example: PHOS-03
        document_type:
          type: string
          description: 'Authoring profile of the component: stig components author STIG requirements, srg components author SRG requirements. Immutable after creation.'
          enum:
            - stig
            - srg
          example: stig
        version:
          type:
            - integer
            - 'null'
          description: Version number of the component. Null if not yet set.
          example: 1
        release:
          type:
            - integer
            - 'null'
          description: Release number of the component. Null if not yet set.
          example: 1
        based_on_title:
          type:
            - string
            - 'null'
          description: Title of the SRG this component is based on. Null if the SRG has no title or component has no SRG.
          example: VMware vSphere 7.0 STIG Readiness Guide
        based_on_version:
          type:
            - string
            - 'null'
          description: Version of the SRG this component is based on. Null if no SRG.
          example: '1'
        severity_counts:
          type: object
          description: Count of rules at each severity level within this component.
          properties:
            high:
              type: integer
              example: 20
            medium:
              type: integer
              example: 173
            low:
              type: integer
              example: 10
          example:
            high: 20
            medium: 173
            low: 10
        pending_comment_count:
          type: integer
          description: Number of pending (untriaged) top-level comments on this component. Pre-batched via Component.pending_comment_counts.
          example: 3
    ComponentIndexResponse:
      description: Component listing entry for project pages. Serialized by ComponentBlueprint :index view. Extends ComponentSummary (default fields) with index-specific fields.
      allOf:
        - $ref: '#/components/schemas/ComponentSummary'
        - type: object
          properties:
            updated_at:
              type: string
              description: Timestamp when the component was last modified. Format is "YYYY-MM-DD HH:MM:SS UTC" (Blueprinter default).
              example: 2026-05-29 15:36:06 UTC
            released:
              type: boolean
              description: Whether this component has been marked as released (finalized).
              example: true
            rules_count:
              type: integer
              description: Number of requirement rows (security controls) in this component — rules for stig-kind components, authored requirements for srg-kind components.
              example: 203
            component_id:
              type:
                - integer
                - 'null'
              description: ID of the source component if this is an overlay. Null for original components.
              example: null
    ProjectShowResponse:
      description: Full project detail for the project show page. Serialized by ProjectBlueprint :show view. 18 total fields including nested components, memberships, users, access_requests.
      allOf:
        - $ref: '#/components/schemas/ProjectSummary'
        - type: object
          properties:
            effective_permissions:
              type:
                - string
                - 'null'
              description: Current user's role on this project (admin, reviewer, author, viewer). Null when the user is not a member. System admins always get 'admin'.
              enum:
                - admin
                - reviewer
                - author
                - viewer
                - null
              example: admin
            pending_comment_count:
              type: integer
              description: Aggregate pending comment count across all components.
              example: 1
            details:
              description: Project-wide requirement statistics. Per-document-type sections (stig with the five DISA buckets, srg with the three SRG buckets) never collapse into each other; total, lock, and review-state numbers are type-agnostic and span every kind.
              oneOf:
                - $ref: '#/components/schemas/ProjectDetails'
                - type: 'null'
            histories:
              type: array
              description: Recent audit trail entries (last 50).
              items:
                type: object
            metadata:
              type:
                - object
                - 'null'
              description: Project metadata key-value pairs.
              example: null
            memberships:
              type: array
              description: Project members with roles.
              items:
                $ref: '#/components/schemas/MembershipSummary'
            components:
              type: array
              description: Components in this project (ComponentBlueprint :index view).
              items:
                $ref: '#/components/schemas/ComponentIndexResponse'
            available_components:
              type: array
              description: Components available to add (from other projects the user can access).
              items:
                $ref: '#/components/schemas/ComponentIndexResponse'
            users:
              type: array
              description: All users associated with this project.
              items:
                type: object
                properties:
                  id:
                    type: integer
                  name:
                    type:
                      - string
                      - 'null'
                  email:
                    type: string
                    format: email
            access_requests:
              type: array
              description: Pending access requests from non-member users.
              items:
                type: object
                properties:
                  id:
                    type: integer
                    description: Access request ID.
                  user:
                    type: object
                    properties:
                      id:
                        type: integer
                      name:
                        type:
                          - string
                          - 'null'
                      email:
                        type: string
                        format: email
                  project_id:
                    type: integer
    ToastResponse:
      description: Canonical mutation response returned by all POST/PUT/PATCH/DELETE endpoints that return only a toast notification (no additional data).
      type: object
      required:
        - toast
      additionalProperties: false
      properties:
        toast:
          $ref: '#/components/schemas/ToastObject'
    ImportSummary:
      description: Summary of what was imported (or would be imported in dry_run mode) from a JSON archive backup. Returned by JsonArchiveImporter.
      type: object
      properties:
        dry_run:
          type: boolean
          description: Whether this was a preview (true) or real import (false). Only present in dry_run responses.
          example: true
        components_imported:
          type: integer
          description: Number of components imported.
          example: 2
        rules_imported:
          type: integer
          description: Total rules imported across all components.
          example: 264
        satisfactions_imported:
          type: integer
          description: Number of rule satisfaction relationships imported.
          example: 12
        reviews_imported:
          type: integer
          description: Number of review records imported.
          example: 48
        memberships_imported:
          type: integer
          description: Number of project memberships imported.
          example: 3
        srgs_imported:
          type: integer
          description: Number of SRGs auto-imported from the archive.
          example: 1
        srg_details:
          type: array
          description: Details of SRGs found in the archive (dry_run only).
          items:
            type: object
            properties:
              title:
                type: string
              version:
                type: string
        component_details:
          type: array
          description: Per-component breakdown of what was imported.
          items:
            type: object
            properties:
              name:
                type: string
                example: Photon OS 3
              rule_count:
                type: integer
                example: 132
              srg_title:
                type:
                  - string
                  - 'null'
                example: General Purpose Operating System SRG
              srg_version:
                type:
                  - string
                  - 'null'
                example: V3R3
    CreateFromBackupDryRunResponse:
      description: Response from a dry-run preview of creating a project from backup. Returns import summary, warnings, and extracted project defaults.
      type: object
      required:
        - summary
        - warnings
        - project_defaults
      properties:
        summary:
          $ref: '#/components/schemas/ImportSummary'
        warnings:
          type: array
          description: Non-fatal warnings found during preview.
          items:
            type: string
          example: []
        project_defaults:
          type: object
          description: Default values extracted from the archive for pre-filling the create form.
          properties:
            name:
              type:
                - string
                - 'null'
              description: Original project name from the archive.
              example: My Original Project
            description:
              type:
                - string
                - 'null'
              description: Original project description.
              example: A test project
            visibility:
              type:
                - string
                - 'null'
              description: Original project visibility setting.
              example: discoverable
    CreateFromBackupResponse:
      description: Response from actually creating a project from a backup archive. Returns redirect URL, import summary, and toast notification.
      type: object
      required:
        - redirect_url
        - summary
        - toast
      properties:
        redirect_url:
          type: string
          description: URL to redirect to after successful project creation.
          example: /projects/123
        summary:
          $ref: '#/components/schemas/ImportSummary'
        toast:
          $ref: '#/components/schemas/ToastObject'
    ImportBackupResponse:
      description: Response from importing a JSON archive backup into an existing project. Includes the toast notification, import summary, and any warnings.
      type: object
      required:
        - toast
        - summary
        - warnings
      properties:
        toast:
          $ref: '#/components/schemas/ToastObject'
        summary:
          $ref: '#/components/schemas/ImportSummary'
        warnings:
          type: array
          description: Non-fatal warnings encountered during import.
          items:
            type: string
          example: []
    AuditEntry:
      description: 'A single audit trail entry recording a change to an auditable record. Serialized by VulcanAudit#format. Note: the field is "name" (username), NOT "user_id" — VulcanAudit#format does not expose user IDs.'
      type: object
      required:
        - id
        - action
        - auditable_type
        - auditable_id
        - created_at
        - audited_changes
      additionalProperties: false
      properties:
        id:
          type: integer
          description: Unique audit entry identifier.
          example: 224
        action:
          type: string
          description: Type of change that was made.
          enum:
            - create
            - update
            - destroy
          example: update
        auditable_type:
          type: string
          description: Rails model class name of the audited record.
          example: Component
        auditable_id:
          type: integer
          description: Primary key of the audited record.
          example: 1
        name:
          type:
            - string
            - 'null'
          description: Display name of the user who made the change. Null for system-initiated changes or seed data.
          example: Demo Admin
        audited_name:
          type:
            - string
            - 'null'
          description: Display name of the audited record's owner. Null when not applicable.
          example: null
        comment:
          type:
            - string
            - 'null'
          description: Audit comment text (e.g. reason for admin action). Null for most changes.
          example: null
        created_at:
          type: string
          description: Timestamp when the audit entry was recorded.
          example: 2026-05-19 14:07:49 UTC
        audited_changes:
          type: array
          description: Array of changed attributes with before/after values. Each entry has a field name, previous value, and new value.
          items:
            type: object
            required:
              - field
            properties:
              field:
                type: string
                description: Name of the changed attribute. For AdditionalAnswer changes, this is the question name rather than the column name.
                example: released
              prev_value:
                description: Previous value before the change. Null for create actions.
                example: false
              new_value:
                description: New value after the change.
                example: true
    ComponentInput:
      description: Input fields for creating or updating a component within a project.
      type: object
      required:
        - name
        - prefix
        - title
        - security_requirements_guide_id
      properties:
        security_requirements_guide_id:
          type: integer
          description: 'The PRIMARY source SRG (based_on). Always a declared source: it joins the declared source set automatically even when declared_source_srg_ids omits it. STIG-kind components derive from non-core SRGs, SRG-kind components from core SRGs.'
          example: 1
        declared_source_srg_ids:
          type: array
          description: The full declared source-SRG set for multi-parent derivation. Every listed SRG becomes a declared parent and contributes its requirements at creation (full union by default). Eligibility is enforced per document_type — non-core SRGs for stig, core SRGs for srg. Omit for the classic single-source create.
          items:
            type: integer
          example:
            - 1
            - 2
        requirement_selections:
          type: object
          description: Selective-import filter. Keys are declared source SRG ids, values the requirement versions to import from that source. When present, only the listed requirements are imported and a declared source absent from the map contributes none; when omitted, creation imports the full union of every declared source.
          additionalProperties:
            type: array
            items:
              type: string
          example:
            '1':
              - SRG-OS-000001
              - SRG-OS-000002
            '2':
              - SRG-APP-000101
        name:
          type: string
          description: Human-readable component name.
          example: Container SRG
        prefix:
          type: string
          description: Short alphanumeric prefix used to construct rule IDs (e.g. CNTR in CNTR-00-000050).
          example: CNTR
        document_type:
          type: string
          description: 'Authoring profile for the new component: stig authors STIG requirements from a source SRG; srg authors SRG requirements. Defaults to stig; immutable after creation.'
          enum:
            - stig
            - srg
          example: stig
        version:
          type: integer
          description: Version number of the component.
          example: 1
        release:
          type: integer
          description: Release number of the component.
          example: 1
        title:
          type: string
          description: Full official title of the component.
          example: Container Platform Security Technical Implementation Guide
        description:
          type: string
          description: Optional longer description of the component scope and purpose.
          example: STIG guidance for container orchestration platform deployments.
        advanced_fields:
          type: boolean
          description: Whether the editor shows the advanced field set for this component.
          example: false
        additional_questions_attributes:
          type: array
          description: Nested additional-question definitions — the same shape the update path accepts, minus id/_destroy (meaningless at creation and not accepted). On the duplicate path, provided questions REPLACE the ones copied from the source component.
          items:
            type: object
            properties:
              name:
                type: string
                example: Deployment environment
              question_type:
                type: string
                enum:
                  - dropdown
                  - freeform
                  - url
                example: dropdown
              options:
                type: array
                description: Choice list — required when question_type is dropdown.
                items:
                  type: string
                example:
                  - Cloud
                  - On-prem
        component_metadata_attributes:
          type: object
          description: Nested metadata payload; data is a free-form string map — the same shape the update path accepts. On the duplicate path, provided metadata replaces the copied record. The slack_channel_id convenience field merges into this map as "Slack Channel ID" and wins over a same-key entry.
          properties:
            data:
              type: object
              additionalProperties:
                type: string
              example:
                Vendor: Acme
                POC: Sam
        slack_channel_id:
          type: string
          description: Convenience field — stored as the metadata key "Slack Channel ID". Accepted at creation only; use component_metadata_attributes on update.
          example: C0123456789
    RuleSummary:
      description: Base rule fields shared by all views. Serialized by RuleBlueprint (default view / navigator). View-specific schemas (RuleEditorResponse, RulePickerResponse) extend this with additional fields.
      type: object
      required:
        - id
        - rule_id
        - locked
      properties:
        id:
          type: integer
          description: Unique rule identifier.
          example: 1786
        rule_id:
          type: string
          description: Six-digit zero-padded rule number combined with the component prefix to form the displayed ID (e.g. PHOS-03-000001).
          example: '000001'
        title:
          type:
            - string
            - 'null'
          description: Title of the security control. Null if not yet authored.
          example: The operating system must provide automated mechanisms for supporting account management functions.
        version:
          type:
            - string
            - 'null'
          description: SRG requirement version identifier mapping this rule to its SRG source.
          example: SRG-OS-000001-GPOS-00001
        status:
          type:
            - string
            - 'null'
          description: Current applicability status of the rule. Null if not yet determined.
          enum:
            - Not Yet Determined
            - Applicable - Configurable
            - Applicable - Inherently Meets
            - Applicable - Does Not Meet
            - Not Applicable
            - null
          example: Not Yet Determined
        rule_severity:
          type:
            - string
            - 'null'
          description: DISA severity level of the rule.
          enum:
            - low
            - medium
            - high
            - null
          example: medium
        locked:
          type: boolean
          description: Whether the rule is locked from further edits pending review.
          example: true
        review_requestor_id:
          type:
            - integer
            - 'null'
          description: ID of the user who requested review for this rule. Null if no review requested.
          example: null
        changes_requested:
          type: boolean
          description: Whether changes have been requested on this rule during review.
          example: false
        comment_summary:
          type: object
          description: Per-rule comment counts. Computed in-memory from eager-loaded reviews.
          required:
            - open
            - total
          properties:
            open:
              type: integer
              description: Comments not yet adjudicated (pending, triaged, needs_clarification) including replies under open parents.
              example: 0
            total:
              type: integer
              description: Total comment count across all statuses.
              example: 0
    DisaRuleDescription:
      description: DISA rule description fields — vulnerability discussion, mitigations, and related metadata.
      type: object
      required:
        - id
      additionalProperties: false
      properties:
        id:
          type: integer
          description: Unique description record identifier.
          example: 300
        vuln_discussion:
          type:
            - string
            - 'null'
          description: Vulnerability discussion explaining why this requirement matters.
          example: Without verification, containers may execute untrusted code...
        false_positives:
          type:
            - string
            - 'null'
          description: Known false positive scenarios for this check.
          example: null
        false_negatives:
          type:
            - string
            - 'null'
          description: Known false negative scenarios for this check.
          example: null
        documentable:
          type:
            - boolean
            - 'null'
          description: Whether this requirement is documentable.
          example: false
        mitigations:
          type:
            - string
            - 'null'
          description: Available mitigations if the requirement cannot be met directly.
          example: null
        severity_override_guidance:
          type:
            - string
            - 'null'
          description: Guidance for when severity may be overridden by the assessor.
          example: null
        potential_impacts:
          type:
            - string
            - 'null'
          description: Potential impacts of non-compliance.
          example: null
        third_party_tools:
          type:
            - string
            - 'null'
          description: Third-party tools that can assist with compliance checking.
          example: null
        mitigation_control:
          type:
            - string
            - 'null'
          description: Compensating controls that mitigate the risk.
          example: null
        responsibility:
          type:
            - string
            - 'null'
          description: Who is responsible for implementing this requirement.
          example: null
        ia_controls:
          type:
            - string
            - 'null'
          description: Information assurance controls associated with this requirement.
          example: null
        mitigations_available:
          type:
            - string
            - 'null'
          description: Whether mitigations are available for this requirement.
          example: null
        poam_available:
          type:
            - string
            - 'null'
          description: Whether a Plan of Action and Milestones is available.
          example: null
        poam:
          type:
            - string
            - 'null'
          description: Plan of Action and Milestones content.
          example: null
        _destroy:
          type: boolean
          description: Nested attributes destroy flag (always false in read responses).
          example: false
    CheckSummary:
      description: STIG check content — how to verify a security requirement is met.
      type: object
      required:
        - id
      additionalProperties: false
      properties:
        id:
          type: integer
          description: Unique check identifier.
          example: 200
        system:
          type:
            - string
            - 'null'
          description: Check system identifier (e.g., C-OVAL for automated checks).
          example: C-56947r840354_chk
        content_ref_name:
          type:
            - string
            - 'null'
          description: Reference name for the check content.
          example: M
        content_ref_href:
          type:
            - string
            - 'null'
          description: URL reference for the check content.
          example: DPMS_XCCDF-Container_Platform_SRG.xml
        content:
          type:
            - string
            - 'null'
          description: The check procedure text — steps to verify compliance.
          example: Verify the container platform restricts access to container images...
        _destroy:
          type: boolean
          description: Nested attributes destroy flag (always false in read responses).
          example: false
    SatisfactionSummary:
      description: Rule satisfaction relationship — this rule satisfies (implements) an SRG requirement.
      type: object
      required:
        - id
        - rule_id
      additionalProperties: false
      properties:
        id:
          type: integer
          description: Unique satisfaction record identifier.
          example: 50
        rule_id:
          type: integer
          description: ID of the rule that is satisfied.
          example: 100
        srg_id:
          type:
            - string
            - 'null'
          description: SRG requirement version identifier (from the SRG rule's version field).
          example: CNTR-00-000050
    SatisfiedBySummary:
      description: Satisfied-by relationship — extends SatisfactionSummary with the parent rule's fix text.
      type: object
      required:
        - id
        - rule_id
      additionalProperties: false
      properties:
        id:
          type: integer
          description: Unique satisfaction record identifier.
          example: 50
        rule_id:
          type: integer
          description: ID of the parent rule that satisfies this one.
          example: 100
        srg_id:
          type:
            - string
            - 'null'
          description: SRG requirement version identifier.
          example: CNTR-00-000050
        fixtext:
          type:
            - string
            - 'null'
          description: Fix text from the parent rule (inherited content).
          example: Configure the container platform to restrict access...
    RuleDescriptionSummary:
      description: Rule description record — the HTML/text description block for a rule.
      type: object
      required:
        - id
      additionalProperties: false
      properties:
        id:
          type: integer
          description: Unique description record identifier.
          example: 400
        description:
          type:
            - string
            - 'null'
          description: The rule description text (may contain HTML from XCCDF source).
          example: <VulnDiscussion>Without verification, containers may execute untrusted code.</VulnDiscussion>
        _destroy:
          type: boolean
          description: Nested attributes destroy flag (always false in read responses).
          example: false
    ReviewSummary:
      description: A review action recorded on a rule. Serialized by ReviewBlueprint (default view). The user_id field is intentionally excluded as a public-comment correlation guard.
      type: object
      required:
        - id
        - action
        - comment
        - rule_id
        - created_at
        - triage_status
        - reactions
      additionalProperties: false
      properties:
        id:
          type: integer
          description: Unique review identifier.
          example: 1
        action:
          type: string
          description: The type of review action that was performed.
          enum:
            - comment
            - request_review
            - revoke_review_request
            - approve
            - lock
          example: comment
        comment:
          type: string
          description: The comment text accompanying this review action.
          example: The check says "verify that TLS 1.2 or greater is being used" but does not specify HOW to verify.
        created_at:
          type: string
          description: Timestamp when the review was created. Format is "YYYY-MM-DD HH:MM:SS UTC" (Blueprinter default).
          example: 2026-05-19 14:08:17 UTC
        triage_status:
          type:
            - string
            - 'null'
          description: Current triage disposition of the comment.
          enum:
            - pending
            - concur
            - concur_with_comment
            - non_concur
            - duplicate
            - informational
            - needs_clarification
            - withdrawn
            - addressed_by
            - null
          example: pending
        triage_set_at:
          type:
            - string
            - 'null'
          description: Timestamp when the triage status was last changed. Null if still pending.
          example: null
        adjudicated_at:
          type:
            - string
            - 'null'
          description: Timestamp when the comment reached a terminal triage state. Null if not yet adjudicated.
          example: null
        rule_id:
          type:
            - integer
            - 'null'
          description: ID of the rule this review is attached to. Null for component-scoped reviews.
          example: 2976
        section:
          type:
            - string
            - 'null'
          description: Rule section the comment applies to (e.g. fixtext, check_content, vuln_discussion). Null for general comments.
          example: check_content
        responding_to_review_id:
          type:
            - integer
            - 'null'
          description: ID of the parent review if this is a threaded reply. Null for top-level reviews.
          example: null
        duplicate_of_review_id:
          type:
            - integer
            - 'null'
          description: ID of the review this is marked as a duplicate of. Null if not a duplicate.
          example: null
        addressed_by_rule_id:
          type:
            - integer
            - 'null'
          description: ID of the rule that addresses this comment. Set when triage_status is addressed_by. Null otherwise.
          example: null
        triage_set_by_id:
          type:
            - integer
            - 'null'
          description: ID of the user who last changed the triage status. Null if still pending.
          example: null
        name:
          type:
            - string
            - 'null'
          description: Display name of the review author (delegated from User).
          example: Demo Viewer
        author_name:
          type:
            - string
            - 'null'
          description: Display name of the review author (explicit field, same value as name).
          example: Demo Viewer
        triager_display_name:
          type:
            - string
            - 'null'
          description: Display name of the user who triaged this comment. Null if not yet triaged. Uses ImportedAttribution resolution (DB user name → imported name → imported email).
          example: null
        triager_imported:
          type: boolean
          description: Whether the triager attribution came from an import (no matching DB user).
          example: false
        adjudicator_display_name:
          type:
            - string
            - 'null'
          description: Display name of the user who adjudicated this comment. Null if not yet adjudicated.
          example: null
        adjudicator_imported:
          type: boolean
          description: Whether the adjudicator attribution came from an import.
          example: false
        commenter_display_name:
          type:
            - string
            - 'null'
          description: Display name of the comment author. Uses ImportedAttribution resolution.
          example: Demo Viewer
        commenter_imported:
          type: boolean
          description: Whether the commenter attribution came from an import.
          example: false
        commentable_type:
          type:
            - string
            - 'null'
          description: Polymorphic type — BaseRule for rule-scoped, Component for component-scoped.
          example: BaseRule
        responses_count:
          type: integer
          description: Number of direct replies to this review.
          example: 0
        rule_displayed_name:
          type:
            - string
            - 'null'
          description: Human-readable rule label (PREFIX-RULE_ID). Populated when the serializer is called with rule_names option. Null otherwise.
          example: PHOS-03-000001
        author_email:
          type:
            - string
            - 'null'
          format: email
          description: Author's email. Only present when include_email option is true (admin-tier surfaces). Null or absent for public endpoints.
          example: null
        commenter_email:
          type:
            - string
            - 'null'
          format: email
          description: Commenter's email (same as author_email but follows the commenter attribution naming convention). Only present when include_email option is true. Null for imported reviews with no matching DB user.
          example: null
        reactions:
          type: object
          description: Aggregate reaction counts and current user's reaction for this review.
          required:
            - up
            - down
          properties:
            up:
              type: integer
              description: Number of upvote reactions.
              example: 1
            down:
              type: integer
              description: Number of downvote reactions.
              example: 0
            mine:
              type:
                - string
                - 'null'
              description: The current user's reaction type (up or down). Null if no active reaction.
              example: null
    AdditionalAnswerSummary:
      description: Answer to an additional question on a component rule.
      type: object
      required:
        - id
        - additional_question_id
      additionalProperties: false
      properties:
        id:
          type: integer
          description: Unique answer identifier.
          example: 10
        additional_question_id:
          type: integer
          description: ID of the question this answers.
          example: 3
        answer:
          type:
            - string
            - 'null'
          description: The answer text provided by the author.
          example: Yes, this applies to all container runtime environments.
    SrgRuleSummary:
      description: SRG rule — the original SRG requirement that a component rule implements.
      type: object
      required:
        - id
        - rule_id
        - title
      additionalProperties: false
      properties:
        id:
          type: integer
          description: Unique SRG rule identifier.
          example: 500
        rule_id:
          type: string
          description: DISA rule identifier (e.g., SV-222xxx_rule).
          example: SV-222396r857506_rule
        title:
          type: string
          description: Requirement title.
          example: The container platform must enforce approved authorizations for access.
        version:
          type:
            - string
            - 'null'
          description: SRG version identifier.
          example: CNTR-00-000050
        rule_severity:
          type:
            - string
            - 'null'
          description: Severity level.
          enum:
            - low
            - medium
            - high
          example: medium
        rule_weight:
          type:
            - string
            - 'null'
          description: Rule weight for scoring.
          example: '10.0'
        ident:
          type:
            - string
            - 'null'
          description: CCI identifiers (comma-separated).
          example: CCI-000213
        ident_system:
          type:
            - string
            - 'null'
          description: Identifier system URI.
          example: http://cyber.mil/cci
        fixtext:
          type:
            - string
            - 'null'
          description: Fix text describing how to remediate.
          example: Configure the container platform to enforce approved authorizations...
        fixtext_fixref:
          type:
            - string
            - 'null'
          description: Fix reference identifier.
          example: F-25073r857505_fix
        fix_id:
          type:
            - string
            - 'null'
          description: Fix identifier.
          example: F-25073r857505_fix
        inspec_control_body:
          type:
            - string
            - 'null'
          description: InSpec control Ruby source code.
          example: null
        inspec_control_file:
          type:
            - string
            - 'null'
          description: InSpec control file path.
          example: null
        inspec_control_body_lang:
          type:
            - string
            - 'null'
          description: Language of the InSpec control body (usually ruby).
          example: null
        inspec_control_file_lang:
          type:
            - string
            - 'null'
          description: Language of the InSpec control file.
          example: null
        vuln_id:
          type:
            - string
            - 'null'
          description: Vulnerability identifier (V-number).
          example: V-222396
        legacy_ids:
          type:
            - string
            - 'null'
          description: Legacy rule identifiers from previous STIG versions (comma-separated string).
          example: SV-42474, V-32157
        rule_descriptions_attributes:
          type: array
          description: Rule description records.
          items:
            $ref: '#/components/schemas/RuleDescriptionSummary'
        disa_rule_descriptions_attributes:
          type: array
          description: DISA rule description records (vuln discussion, mitigations, etc.).
          items:
            $ref: '#/components/schemas/DisaRuleDescription'
        checks_attributes:
          type: array
          description: Check content records.
          items:
            $ref: '#/components/schemas/CheckSummary'
    RuleEditorResponse:
      description: Full rule for the editing form. Serialized by RuleBlueprint :editor view. Includes viewer fields (text content, nested associations) plus editor-only fields (InSpec, reviews, additional answers, SRG source data). 38 total fields. Used by GET /rules/:id, GET /components/:id/rules, and nested inside ComponentEditorResponse.
      allOf:
        - $ref: '#/components/schemas/RuleSummary'
        - type: object
          required:
            - satisfies
            - satisfied_by
          properties:
            rule_weight:
              type:
                - string
                - 'null'
              description: Rule weight for DISA scoring.
              example: '10.0'
            fixtext:
              type:
                - string
                - 'null'
              description: Remediation instructions.
              example: Configure the operating system to provide automated mechanisms...
            fixtext_fixref:
              type:
                - string
                - 'null'
              description: Fix reference identifier.
              example: F-3716r557030_fix
            ident:
              type:
                - string
                - 'null'
              description: CCI identifiers (comma-separated).
              example: CCI-000015
            ident_system:
              type:
                - string
                - 'null'
              description: Identifier system URI.
              example: http://cyber.mil/cci
            vendor_comments:
              type:
                - string
                - 'null'
              description: Vendor-provided implementation notes.
              example: null
            vuln_id:
              type:
                - string
                - 'null'
              description: Vulnerability identifier (V-number).
              example: null
            legacy_ids:
              type:
                - string
                - 'null'
              description: Legacy rule identifiers (comma-separated string).
              example: V-56571, SV-70831
            component_id:
              type: integer
              description: ID of the component this rule belongs to.
              example: 1
            status_justification:
              type:
                - string
                - 'null'
              description: Justification text for NA or DNM status.
              example: null
            artifact_description:
              type:
                - string
                - 'null'
              description: Description of compliance evidence artifacts.
              example: null
            locked_fields:
              type: object
              description: Map of section names to locked state. Empty object means no sections locked.
              additionalProperties:
                type: boolean
              example: {}
            nist_control_family:
              type:
                - string
                - 'null'
              description: NIST SP 800-53 control family mapping.
              example: AC-2 (1)
            srg_id:
              type:
                - string
                - 'null'
              description: SRG requirement version this rule implements.
              example: SRG-OS-000001-GPOS-00001
            inspec_control_body:
              type:
                - string
                - 'null'
              description: InSpec control Ruby source code.
              example: null
            inspec_control_file:
              type:
                - string
                - 'null'
              description: InSpec control file path.
              example: null
            inspec_control_body_lang:
              type:
                - string
                - 'null'
              description: Language of the InSpec control body.
              example: ruby
            inspec_control_file_lang:
              type:
                - string
                - 'null'
              description: Language of the InSpec control file.
              example: ruby
            fix_id:
              type:
                - string
                - 'null'
              description: Fix identifier.
              example: F-3716r557030_fix
            disa_rule_descriptions_attributes:
              type: array
              description: DISA rule description records.
              items:
                $ref: '#/components/schemas/DisaRuleDescription'
            checks_attributes:
              type: array
              description: Check content records.
              items:
                $ref: '#/components/schemas/CheckSummary'
            satisfies:
              type: array
              description: Rules that this rule satisfies (child-to-parent relationships).
              items:
                $ref: '#/components/schemas/SatisfactionSummary'
            satisfied_by:
              type: array
              description: Rules that satisfy this rule (parent-to-child relationships).
              items:
                $ref: '#/components/schemas/SatisfiedBySummary'
            histories:
              type: array
              description: Audit trail entries for this rule, ordered by creation time. Present only when a single rule is requested. The trail cannot be fetched for a whole collection in one query, so a component response omits it for every rule and callers read it from the per-rule endpoint for the one being shown.
              items:
                $ref: '#/components/schemas/AuditEntry'
            rule_descriptions_attributes:
              type: array
              description: Raw rule description records.
              items:
                $ref: '#/components/schemas/RuleDescriptionSummary'
            reviews:
              type: array
              description: Review actions on this rule (comments, approvals, etc.).
              items:
                $ref: '#/components/schemas/ReviewSummary'
            additional_answers_attributes:
              type: array
              description: Answers to component-level additional questions.
              items:
                $ref: '#/components/schemas/AdditionalAnswerSummary'
            srg_rule_attributes:
              description: Source SRG rule data (the baseline requirement this rule implements). Null if no SRG rule linked.
              oneOf:
                - $ref: '#/components/schemas/SrgRuleSummary'
                - type: 'null'
            srg_info:
              type:
                - object
                - 'null'
              description: SRG version metadata.
              properties:
                version:
                  type:
                    - string
                    - 'null'
                  example: V2R4
    AuthoredSrgRuleEditorResponse:
      description: 'A component-authored SRG requirement for the editing form. Shares the common requirement surface with STIG rules — status, locking, reviews, audit history, content — but its status vocabulary is the three-value SRG set, and it carries none of the STIG-only surfaces: no satisfies/satisfied_by, no srg_rule_attributes, no additional answers, no InSpec fields. Lineage is the portable derived_from_version identifier.'
      type: object
      required:
        - id
        - rule_id
        - locked
        - srg_id
        - derived_from_version
      additionalProperties: false
      properties:
        id:
          type: integer
          description: Unique requirement identifier.
          example: 10633
        rule_id:
          type: string
          description: Six-digit zero-padded requirement number combined with the component prefix to form the displayed ID.
          example: '000001'
        title:
          type:
            - string
            - 'null'
          description: Title of the requirement. Null if not yet authored.
          example: The container platform must enforce approved authorizations.
        version:
          type:
            - string
            - 'null'
          description: Requirement version identifier. Minted at release for authored requirements.
          example: null
        status:
          type:
            - string
            - 'null'
          description: Applicability status in the SRG authoring vocabulary.
          enum:
            - Not Yet Determined
            - Applicable
            - Not Applicable
            - null
          example: Applicable
        rule_severity:
          type:
            - string
            - 'null'
          description: DISA severity level.
          enum:
            - low
            - medium
            - high
            - null
          example: medium
        locked:
          type: boolean
          description: Whether the requirement is locked from further edits.
          example: false
        review_requestor_id:
          type:
            - integer
            - 'null'
          description: ID of the user who requested review. Null if no review requested.
          example: null
        changes_requested:
          type: boolean
          description: Whether changes have been requested during review.
          example: false
        comment_summary:
          type: object
          description: Per-requirement comment counts.
          required:
            - open
            - total
          additionalProperties: false
          properties:
            open:
              type: integer
              description: Comments not yet adjudicated, including replies under open parents.
              example: 1
            total:
              type: integer
              description: Total comment count across all statuses.
              example: 1
        rule_weight:
          type:
            - string
            - 'null'
          description: Rule weight for DISA scoring.
          example: '10.0'
        fixtext:
          type:
            - string
            - 'null'
          description: Remediation guidance authored for this requirement.
          example: Configure the container platform to enforce approved authorizations.
        fixtext_fixref:
          type:
            - string
            - 'null'
          description: Fix reference identifier.
          example: null
        ident:
          type:
            - string
            - 'null'
          description: Comma-separated CCI identifiers.
          example: CCI-000366
        ident_system:
          type:
            - string
            - 'null'
          description: Identifier system URI.
          example: http://iase.disa.mil/cci
        vendor_comments:
          type:
            - string
            - 'null'
          description: Vendor comments.
          example: null
        vuln_id:
          type:
            - string
            - 'null'
          description: Vulnerability identifier. Assigned at release for authored requirements.
          example: null
        legacy_ids:
          type:
            - string
            - 'null'
          description: Comma-separated legacy identifiers.
          example: null
        component_id:
          type: integer
          description: The SRG-kind component this requirement is authored on.
          example: 46
        status_justification:
          type:
            - string
            - 'null'
          description: Justification for the chosen status.
          example: null
        artifact_description:
          type:
            - string
            - 'null'
          description: Artifact description.
          example: null
        locked_fields:
          type: object
          description: Per-section lock map (section name to true).
          example: {}
        nist_control_family:
          type: string
          description: NIST control families derived from the CCI identifiers.
          example: CM
        srg_id:
          type:
            - string
            - 'null'
          description: The SRG requirement identifier shown by requirement lists (the editor sidebar's SRG ID display) — one kind-agnostic key across document kinds. For an authored requirement this is the row's version value, the same identifier the editor header displays. Null when the row has no requirement identifier yet.
          example: SRG-APP-000003
        derived_from_version:
          type:
            - string
            - 'null'
          description: Version (SRG-ID) of the core-catalog requirement this authored requirement was derived from. Null for requirements authored from scratch. Always present — it distinguishes authored requirements from STIG rules wherever the two shapes appear in a oneOf.
          example: SRG-OS-000023
        disa_rule_descriptions_attributes:
          type: array
          description: DISA rule description records.
          items:
            $ref: '#/components/schemas/DisaRuleDescription'
        checks_attributes:
          type: array
          description: Check content records.
          items:
            $ref: '#/components/schemas/CheckSummary'
        rule_descriptions_attributes:
          type: array
          description: Raw rule description records.
          items:
            $ref: '#/components/schemas/RuleDescriptionSummary'
        histories:
          type: array
          description: Audit trail entries for this requirement, ordered by creation time. Present only when a single requirement is requested. The trail cannot be fetched for a whole collection in one query, so a component response omits it for every requirement and callers read it from the per-requirement endpoint for the one being shown.
          items:
            $ref: '#/components/schemas/AuditEntry'
        reviews:
          type: array
          description: Review actions on this requirement (comments, locks, etc.).
          items:
            $ref: '#/components/schemas/ReviewSummary'
    ComponentEditorResponse:
      description: Full component for the editing page. Serialized by ComponentBlueprint :editor view. 35 total fields including nested rules, memberships, histories, reviews. Used by GET /components/:id (member), GET /components/:id/rules (via index).
      allOf:
        - $ref: '#/components/schemas/ComponentSummary'
        - type: object
          properties:
            effective_permissions:
              type:
                - string
                - 'null'
              description: Current user's role on this component's project (admin, reviewer, author, viewer). Null when user is not a member. System admins always get 'admin'.
              enum:
                - admin
                - reviewer
                - author
                - viewer
                - null
              example: admin
            title:
              type:
                - string
                - 'null'
              description: Full official title of the component.
              example: Photon OS 3 STIG Readiness Guide
            description:
              type:
                - string
                - 'null'
              description: Optional longer description.
              example: null
            admin_name:
              type:
                - string
                - 'null'
              description: Point of contact name.
              example: Photon OS Maintainer
            admin_email:
              type:
                - string
                - 'null'
              format: email
              description: Point of contact email.
              example: photon-team@example.org
            released:
              type: boolean
              description: Whether finalized.
              example: true
            advanced_fields:
              type: boolean
              description: Whether advanced DISA fields are shown in the editor.
              example: false
            project_id:
              type: integer
              description: ID of the parent project.
              example: 1
            component_id:
              type:
                - integer
                - 'null'
              description: ID of the source component if this is an overlay. Null for originals.
              example: null
            security_requirements_guide_id:
              type: integer
              description: ID of the SRG this component is based on.
              example: 3
            memberships_count:
              type: integer
              description: Number of direct memberships on this component.
              example: 0
            rules_count:
              type: integer
              description: Number of requirement rows in this component — rules for stig-kind components, authored requirements for srg-kind components.
              example: 203
            updated_at:
              type: string
              description: Last modified timestamp. Blueprinter format.
              example: 2026-05-29 15:36:06 UTC
            created_at:
              type: string
              description: Creation timestamp. Blueprinter format.
              example: 2026-05-19 14:07:48 UTC
            comment_phase:
              type:
                - string
                - 'null'
              description: Current comment period phase.
              enum:
                - draft
                - open
                - closed
                - null
              example: open
            closed_reason:
              type:
                - string
                - 'null'
              description: Reason the comment period was closed.
              example: null
            comment_period_starts_at:
              type:
                - string
                - 'null'
              description: Start of the public comment period.
              example: null
            comment_period_ends_at:
              type:
                - string
                - 'null'
              description: End of the public comment period.
              example: null
            releasable:
              type: boolean
              description: Whether all rules meet release criteria.
              example: false
            srg_is_latest:
              type: boolean
              description: Whether EVERY declared source SRG of the component is at its latest release. A component with any stale parent — including a secondary parent under dual lineage — reports false.
              example: true
            srg_latest_version:
              type:
                - string
                - 'null'
              description: Version string of the newest release available for the first stale parent (primary first, then secondaries). Null when srg_is_latest=true.
              example: null
            srg_latest_id:
              type:
                - integer
                - 'null'
              description: ID of that newest release for linking. Null when srg_is_latest=true.
              example: null
            status_counts:
              description: 'Requirement counts bucketed by the component''s document_type: five STIG buckets or three SRG buckets. The shapes are disjoint and never collapse into one list.'
              oneOf:
                - $ref: '#/components/schemas/RulesByStatus'
                - $ref: '#/components/schemas/SrgRulesByStatus'
            moved_out_count:
              type: integer
              description: Requirements that relocated OUT of this component — a lifecycle fact counted from executed relocation records, never one of the status buckets. Always 0 for stig-kind components.
              example: 0
            additional_questions:
              type: array
              description: Component-level custom questions for rule authors.
              items:
                type: object
            rules:
              type: array
              description: 'All requirements, shaped by document_type: STIG rule objects for stig components, authored SRG requirement objects for srg components.'
              items:
                oneOf:
                  - $ref: '#/components/schemas/RuleEditorResponse'
                  - $ref: '#/components/schemas/AuthoredSrgRuleEditorResponse'
            reviews:
              type: array
              description: Recent reviews (last 20, hand-built hashes with displayed_rule_name).
              items:
                type: object
            histories:
              type: array
              description: Audit trail entries (last 50).
              items:
                type: object
            memberships:
              type: array
              description: Direct component memberships.
              items:
                $ref: '#/components/schemas/MembershipSummary'
            metadata:
              type:
                - object
                - 'null'
              description: Component metadata key-value pairs.
              example: null
            inherited_memberships:
              type: array
              description: Memberships inherited from the parent project.
              items:
                $ref: '#/components/schemas/MembershipSummary'
    ComponentShowResponse:
      description: Component detail for non-member read-only view. Serialized by ComponentBlueprint :show view. Includes rules (viewer view), reviews, and effective_permissions.
      allOf:
        - $ref: '#/components/schemas/ComponentSummary'
        - type: object
          properties:
            effective_permissions:
              type:
                - string
                - 'null'
              description: Current user's role on this component's project (admin, reviewer, author, viewer). Null when user is not a member.
              enum:
                - admin
                - reviewer
                - author
                - viewer
                - null
              example: viewer
            title:
              type:
                - string
                - 'null'
              description: Full official title of the component.
              example: Photon OS 3 STIG Readiness Guide
            description:
              type:
                - string
                - 'null'
              description: Optional longer description.
              example: null
            admin_name:
              type:
                - string
                - 'null'
              description: Point of contact name.
              example: Jane Doe
            admin_email:
              type:
                - string
                - 'null'
              format: email
              description: Point of contact email.
              example: jane@example.com
            released:
              type: boolean
              description: Whether the component has been released.
              example: false
            updated_at:
              type: string
              description: Last update timestamp.
              example: 2026-05-28 15:00:00 UTC
            comment_phase:
              type: string
              description: Current comment workflow phase.
              enum:
                - open
                - closed
              example: open
            closed_reason:
              type:
                - string
                - 'null'
              description: Reason for closure (adjudicating or finalized).
              example: null
            comment_period_starts_at:
              type:
                - string
                - 'null'
              description: When the comment period started.
              example: null
            comment_period_ends_at:
              type:
                - string
                - 'null'
              description: When the comment period ends.
              example: null
            rules:
              type: array
              description: Rules in viewer view.
              items:
                type: object
            reviews:
              type: array
              description: Reviews with displayed_rule_name.
              items:
                type: object
    ComponentUpdateInput:
      description: Partial update for an existing component — every field is optional and only provided attributes change. The authoring profile (document_type) is immutable after creation and is not accepted here.
      type: object
      properties:
        name:
          type: string
          description: Human-readable component name.
          example: Container SRG
        prefix:
          type: string
          description: Short alphanumeric prefix used to construct rule IDs (e.g. CNTR in CNTR-00-000050).
          example: CNTR
        version:
          type: integer
          description: Version number of the component.
          example: 1
        release:
          type: integer
          description: Release number of the component.
          example: 1
        title:
          type: string
          description: Full official title of the component.
          example: Container Platform Security Technical Implementation Guide
        description:
          type: string
          description: Optional longer description of the component scope and purpose.
          example: STIG guidance for container orchestration platform deployments.
        released:
          type: boolean
          description: Marks the component as released (admin-gated workflow action).
          example: false
        admin_name:
          type: string
          description: Point-of-contact name.
          example: Jane Analyst
        admin_email:
          type: string
          description: Point-of-contact email.
          example: jane.analyst@example.com
        advanced_fields:
          type: boolean
          description: Whether the editor shows the advanced field set for this component.
          example: false
        comment_phase:
          type: string
          description: Public comment phase for the component.
          enum:
            - draft
            - open
            - triage
            - final
          example: open
        closed_reason:
          type:
            - string
            - 'null'
          description: Reason recorded when the comment phase closes.
          example: null
        comment_period_starts_at:
          type:
            - string
            - 'null'
          format: date-time
          description: Public comment period opening time.
          example: '2026-07-01T00:00:00Z'
        comment_period_ends_at:
          type:
            - string
            - 'null'
          format: date-time
          description: Public comment period closing time.
          example: '2026-08-01T00:00:00Z'
        additional_questions_attributes:
          type: array
          description: Nested additional-question definitions (id + _destroy for removal).
          items:
            type: object
            properties:
              id:
                type: integer
                example: 3
              name:
                type: string
                example: Deployment environment
              question_type:
                type: string
                example: dropdown
              _destroy:
                type: boolean
                example: false
              options:
                type: array
                items:
                  type: string
                example:
                  - Cloud
                  - On-prem
        component_metadata_attributes:
          type: object
          description: Nested metadata payload; data is a free-form string map.
          properties:
            data:
              type: object
              additionalProperties:
                type: string
              example:
                Slack Channel ID: C123456
    RuleContent:
      description: Rule content fields for the split-pane triage view, serialized by Component#serialize_rule_content. Present on comment rows only when the include_rule_content query parameter is true and the comment targets a rule (null for component-level comments).
      type: object
      additionalProperties: false
      properties:
        title:
          type:
            - string
            - 'null'
          description: Rule title.
          example: The container platform must use TLS 1.2 or greater for secure communication.
        rule_severity:
          type:
            - string
            - 'null'
          description: Rule severity.
          example: medium
        status:
          type:
            - string
            - 'null'
          description: Applicability status of the rule.
          example: Applicable - Configurable
        fixtext:
          type:
            - string
            - 'null'
          description: Fix text content.
          example: Configure the container platform to use TLS 1.2 or greater.
        status_justification:
          type:
            - string
            - 'null'
          description: Justification for the current status.
          example: TLS configuration is exposed by the platform and must be set by the operator.
        vendor_comments:
          type:
            - string
            - 'null'
          description: Vendor comments.
          example: Verified against platform version 4.12.
        artifact_description:
          type:
            - string
            - 'null'
          description: Artifact description.
          example: Screenshot of the TLS configuration panel.
        fix_id:
          type:
            - string
            - 'null'
          description: XCCDF fix identifier.
          example: F-59616r921424_fix
        fixtext_fixref:
          type:
            - string
            - 'null'
          description: XCCDF fixtext reference.
          example: F-59616r921424_fix
        version:
          type:
            - string
            - 'null'
          description: STIG/SRG identifier of the rule (document order key).
          example: CNTR-00-000050
        rule_weight:
          type:
            - string
            - 'null'
          description: XCCDF rule weight.
          example: '10.0'
        ident:
          type:
            - string
            - 'null'
          description: CCI identifier string.
          example: CCI-000068
        ident_system:
          type:
            - string
            - 'null'
          description: Identifier system URI.
          example: http://iase.disa.mil/cci
        vuln_discussion:
          type:
            - string
            - 'null'
          description: Vulnerability discussion from the DISA rule description.
          example: Without cryptographic integrity protections, information can be altered in transit.
        documentable:
          type:
            - boolean
            - 'null'
          description: Whether the requirement is documentable.
          example: false
        false_positives:
          type:
            - string
            - 'null'
          description: Known false positives.
          example: null
        false_negatives:
          type:
            - string
            - 'null'
          description: Known false negatives.
          example: null
        mitigations_available:
          type:
            - boolean
            - 'null'
          description: Whether mitigations are available.
          example: false
        mitigations:
          type:
            - string
            - 'null'
          description: Mitigation description.
          example: null
        poam_available:
          type:
            - boolean
            - 'null'
          description: Whether a POA&M is available.
          example: false
        poam:
          type:
            - string
            - 'null'
          description: POA&M description.
          example: null
        potential_impacts:
          type:
            - string
            - 'null'
          description: Potential impacts of applying the fix.
          example: Legacy clients that only support TLS 1.1 will fail to connect.
        third_party_tools:
          type:
            - string
            - 'null'
          description: Third-party tooling notes.
          example: null
        mitigation_control:
          type:
            - string
            - 'null'
          description: Mitigation control description.
          example: null
        responsibility:
          type:
            - string
            - 'null'
          description: Responsible party.
          example: System Administrator
        ia_controls:
          type:
            - string
            - 'null'
          description: IA controls reference.
          example: null
        severity_override_guidance:
          type:
            - string
            - 'null'
          description: Guidance for severity overrides.
          example: null
        check_content:
          type:
            - string
            - 'null'
          description: Check procedure content.
          example: Review the platform TLS configuration and verify the minimum version is 1.2.
        locked:
          type:
            - boolean
            - 'null'
          description: Whether the rule is locked.
          example: false
        rule_updated_at:
          type:
            - string
            - 'null'
          description: ISO 8601 timestamp of the rule's last update, for staleness detection.
          example: '2026-05-19T16:15:00Z'
        satisfied_by:
          type: array
          description: Parent rules that satisfy this rule. Empty for standalone rules. Powers the satisfied-by indicator in the triage rule panel.
          items:
            type: object
            additionalProperties: false
            required:
              - id
              - rule_id
              - component_prefix
            properties:
              id:
                type: integer
                description: Parent rule database identifier.
                example: 1901
              rule_id:
                type: string
                description: Parent rule identifier within the component.
                example: '000020'
              component_prefix:
                type: string
                description: Component prefix used to build the displayed name.
                example: CNTR-00
    CommentRow:
      description: A single comment row in the triage comment listing. Serialized by CommentQueryService#serialize_rows — NOT by ReviewBlueprint. This is a hand-built hash with triage attribution, grouping, and rule context fields.
      type: object
      required:
        - id
        - commentable_type
        - comment
        - created_at
        - triage_status
        - responses_count
        - reactions
      additionalProperties: false
      properties:
        id:
          type: integer
          description: Unique review/comment identifier.
          example: 26
        rule_id:
          type:
            - integer
            - 'null'
          description: ID of the rule this comment is attached to. Null for component-level comments.
          example: 1823
        rule_displayed_name:
          type:
            - string
            - 'null'
          description: Human-readable rule identifier (PREFIX-rule_id). Null or "(component)" for component-level comments.
          example: PHOS-03-000038
        commentable_type:
          type: string
          description: Polymorphic type indicating whether the comment targets a rule or a component.
          enum:
            - BaseRule
            - Component
          example: BaseRule
        section:
          type:
            - string
            - 'null'
          description: Rule section the comment applies to (e.g. fixtext, check_content, vuln_discussion). Null for general or component-level comments.
          example: null
        author_name:
          type:
            - string
            - 'null'
          description: Display name of the comment author (from commenter_display_name).
          example: Demo Viewer
        author_email:
          type:
            - string
            - 'null'
          format: email
          description: Email address of the comment author.
          example: viewer@example.org
        comment:
          type: string
          description: The comment text content.
          example: FYI we already shipped this in our internal hardening guide.
        created_at:
          type: string
          description: Timestamp when the comment was created.
          example: 2026-05-19 14:08:18 UTC
        triage_status:
          type:
            - string
            - 'null'
          description: Current triage disposition of the comment.
          enum:
            - pending
            - concur
            - concur_with_comment
            - non_concur
            - duplicate
            - informational
            - needs_clarification
            - withdrawn
            - addressed_by
            - null
          example: informational
        triage_set_at:
          type:
            - string
            - 'null'
          description: Timestamp when the triage status was last changed. Null if still pending.
          example: 2026-05-19 14:08:18 UTC
        adjudicated_at:
          type:
            - string
            - 'null'
          description: Timestamp when the comment reached a terminal triage state. Null if not yet adjudicated.
          example: 2026-05-19 14:08:18 UTC
        duplicate_of_review_id:
          type:
            - integer
            - 'null'
          description: ID of the review this is marked as a duplicate of. Null if not a duplicate.
          example: null
        addressed_by_rule_id:
          type:
            - integer
            - 'null'
          description: ID of the rule that addresses this comment (for addressed_by triage status). Null otherwise.
          example: null
        srg_info:
          type:
            - object
            - 'null'
          description: SRG baseline metadata for the comment's component — title, version, and whether it's the latest available.
          properties:
            title:
              type: string
              description: Full title of the SRG.
              example: General Purpose Operating System Security Requirements Guide
            version:
              type: string
              description: SRG version in DISA V{major}R{minor} format.
              example: V3R3
            is_latest:
              type: boolean
              description: Whether this SRG version is that SRG's latest available release. Green dot = true, yellow dot = false.
              example: true
        addressed_by_rule_name:
          type:
            - string
            - 'null'
          description: Display name of the rule that addresses this comment. Null if not addressed_by.
          example: null
        triager_display_name:
          type:
            - string
            - 'null'
          description: Display name of the user who triaged this comment. Uses ImportedAttribution resolution.
          example: Demo Author
        triager_imported:
          type: boolean
          description: Whether the triager attribution came from an import (no matching DB user).
          example: false
        adjudicator_display_name:
          type:
            - string
            - 'null'
          description: Display name of the user who adjudicated this comment.
          example: Demo Author
        adjudicator_imported:
          type: boolean
          description: Whether the adjudicator attribution came from an import.
          example: false
        commenter_display_name:
          type:
            - string
            - 'null'
          description: Display name of the comment author. Uses ImportedAttribution resolution.
          example: Demo Viewer
        commenter_imported:
          type: boolean
          description: Whether the commenter attribution came from an import.
          example: false
        responses_count:
          type: integer
          description: Number of threaded replies to this comment.
          example: 0
        reactions:
          type: object
          description: Aggregate reaction counts for this comment (no mine field — unlike ReviewSummary).
          required:
            - up
            - down
          properties:
            up:
              type: integer
              description: Number of upvote reactions.
              example: 0
            down:
              type: integer
              description: Number of downvote reactions.
              example: 0
        updated_at:
          type: string
          description: Timestamp when the comment was last modified.
          example: 2026-05-19 14:08:18 UTC
        rule_status:
          type:
            - string
            - 'null'
          description: Current applicability status of the rule this comment is on. Null for component-level comments.
          example: Not Yet Determined
        parent_rule_displayed_name:
          type:
            - string
            - 'null'
          description: Display name of the parent rule (if this rule is a child via satisfies relationship). Null if no parent.
          example: null
        group_rule_displayed_name:
          type:
            - string
            - 'null'
          description: Grouping key — parent_rule_displayed_name if present, otherwise rule_displayed_name. Used for by-rule grouping in the triage table.
          example: PHOS-03-000038
        rule_content:
          description: Rule content for the split-pane triage view. Present only when the include_rule_content query parameter is true; null for component-level comments.
          oneOf:
            - $ref: '#/components/schemas/RuleContent'
            - type: 'null'
    PaginatedComments:
      description: Paginated list of comments with pagination metadata and triage status counts.
      type: object
      required:
        - rows
        - pagination
        - status_counts
      properties:
        rows:
          type: array
          description: Comment rows for the current page.
          items:
            $ref: '#/components/schemas/CommentRow'
        pagination:
          type: object
          description: Pagination metadata for the comment listing.
          required:
            - page
            - per_page
            - total
            - total_comments
          properties:
            page:
              type: integer
              description: Current page number (1-based).
              example: 1
            per_page:
              type: integer
              description: Number of comments per page.
              example: 25
            total:
              type: integer
              description: Total number of pages available.
              example: 4
            total_comments:
              type: integer
              description: Total number of comments across all pages.
              example: 87
        status_counts:
          type: object
          description: Count of comments grouped by triage status.
          additionalProperties:
            type: integer
          example:
            pending: 12
            concur: 45
            non_concur: 8
            withdrawn: 3
    RuleInput:
      description: 'The full write surface for a requirement — every field the server accepts, shared by the update endpoint and the one-call create body. The endpoint serves both document kinds. Per-kind behavior is noted on the fields that carry it: the status vocabulary is per-kind, and the STIG-only nested attributes (additional_answers_attributes) are dropped server-side when the target is an authored SRG requirement. Nested attributes follow Rails semantics — include id to update an existing record, omit id to create one, send _destroy true to remove (where accepted); at one-call creation ids and _destroy are stripped server-side.'
      type: object
      properties:
        status:
          type: string
          description: Applicability status. The accepted vocabulary depends on the parent component's document_type — STIG rules accept Not Yet Determined, Applicable - Configurable, Applicable - Inherently Meets, Applicable - Does Not Meet, and Not Applicable; authored SRG requirements accept Not Yet Determined, Applicable, and Not Applicable. A value outside the target's vocabulary returns 422.
          enum:
            - Not Yet Determined
            - Applicable - Configurable
            - Applicable - Inherently Meets
            - Applicable - Does Not Meet
            - Applicable
            - Not Applicable
          example: Applicable - Configurable
        status_justification:
          type: string
          description: Justification for the status decision. For an authored SRG requirement the server requires it whenever status is Not Applicable (422 otherwise) — the justification records the decision and is excluded from the released catalog copy.
          example: The platform provides no wireless interfaces to configure.
        title:
          type: string
          description: Title of the security control.
          example: The container platform must enforce approved authorizations for access.
        artifact_description:
          type: string
          description: Description of artifacts or evidence supporting the rule implementation.
          example: Screenshot of the container platform access control configuration panel.
        vendor_comments:
          type: string
          description: Vendor-provided comments or implementation notes for this rule.
          example: This requirement is met by default when using the recommended container runtime configuration.
        rule_severity:
          type: string
          description: Severity category (maps to DISA CAT III / CAT II / CAT I). A value outside the accepted list returns 422.
          enum:
            - low
            - medium
            - high
          example: medium
        rule_weight:
          type: string
          description: Rule weight for DISA scoring.
          example: '10.0'
        version:
          type: string
          description: SRG requirement version identifier mapping this rule to its SRG source.
          example: SRG-OS-000001-GPOS-00001
        ident:
          type: string
          description: CCI identifiers (comma-separated).
          example: CCI-000015
        ident_system:
          type: string
          description: Identifier system URI for the ident value. Server default is http://iase.disa.mil/cci when never set.
          example: http://cyber.mil/cci
        fixtext:
          type: string
          description: Remediation instructions describing how to fix a non-compliant finding.
          example: Configure the container platform to enforce approved authorizations for logical access.
        fix_id:
          type: string
          description: Fix identifier.
          example: F-3716r557030_fix
        fixtext_fixref:
          type: string
          description: Fix reference identifier linking the fixtext to its fix element.
          example: F-3716r557030_fix
        audit_comment:
          type: string
          description: Free-text comment recorded on the audit-trail entry for this change. Not stored on the requirement itself — it annotates the history row that documents who changed what and why.
          example: Adjusted severity per authorizing official direction.
        inspec_control_body:
          type: string
          description: 'InSpec control Ruby source code. InSpec is a stig-kind surface: the server accepts this field on an authored SRG requirement but no read surface returns it for that kind — sending it there writes data nothing serves back.'
          example: |-
            control 'CNTR-00-000050' do
              impact 0.5
            end
        inspec_control_file:
          type: string
          description: InSpec control file path. Stig-kind surface — same write/read asymmetry on authored SRG requirements as inspec_control_body.
          example: controls/CNTR-00-000050.rb
        inspec_control_body_lang:
          type: string
          description: Language of the InSpec control body. Server default is ruby. Stig-kind surface — same write/read asymmetry on authored SRG requirements as inspec_control_body.
          example: ruby
        inspec_control_file_lang:
          type: string
          description: Language of the InSpec control file. Server default is ruby. Stig-kind surface — same write/read asymmetry on authored SRG requirements as inspec_control_body.
          example: ruby
        checks_attributes:
          type: array
          description: Check content records (the XCCDF check element). Rails nested attributes — id updates, no id creates, _destroy true removes.
          items:
            type: object
            properties:
              id:
                type: integer
                description: Existing check record to update. Omit to create a new one.
                example: 311
              system:
                type: string
                description: Check system identifier.
                example: C-Container Platform
              content_ref_name:
                type: string
                description: Name of the referenced check content document.
                example: M
              content_ref_href:
                type: string
                description: Href of the referenced check content document.
                example: DPMS_XCCDF_Benchmark_Container_Platform_SRG.xml
              content:
                type: string
                description: The check procedure text.
                example: Review the container platform configuration to verify approved authorizations are enforced.
              _destroy:
                type: boolean
                description: Send true to delete this check record.
                example: false
        rule_descriptions_attributes:
          type: array
          description: Plain rule description records. Rails nested attributes — id updates, no id creates, _destroy true removes.
          items:
            type: object
            properties:
              id:
                type: integer
                description: Existing description record to update. Omit to create a new one.
                example: 154
              description:
                type: string
                description: The description text.
                example: Enforcing approved authorizations limits lateral movement inside the platform.
              _destroy:
                type: boolean
                description: Send true to delete this description record.
                example: false
        additional_answers_attributes:
          type: array
          description: Answers to the project's additional questions. STIG rules only — dropped server-side when the target is an authored SRG requirement (on update and at one-call creation alike). One answer per question per rule; records are created or updated, never destroyed through this surface (_destroy is not accepted).
          items:
            type: object
            properties:
              id:
                type: integer
                description: Existing answer record to update. Omit to create a new one.
                example: 87
              additional_question_id:
                type: integer
                description: The additional question this answer belongs to.
                example: 3
              answer:
                type: string
                description: The answer text.
                example: Yes — enforced through the platform's admission controller.
        disa_rule_descriptions_attributes:
          type: array
          description: DISA rule description records (the vulnerability discussion and DISA metadata block). Rails nested attributes — id updates, no id creates, _destroy true removes.
          items:
            type: object
            properties:
              id:
                type: integer
                description: Existing DISA description record to update. Omit to create a new one.
                example: 209
              vuln_discussion:
                type: string
                description: Vulnerability discussion text.
                example: Unapproved authorizations allow users to exceed their intended privileges.
              false_positives:
                type: string
                description: Known false-positive conditions for the check.
                example: None identified.
              false_negatives:
                type: string
                description: Known false-negative conditions for the check.
                example: None identified.
              documentable:
                type: boolean
                description: Whether a finding may be addressed through documentation.
                example: false
              mitigations_available:
                type: boolean
                description: Whether mitigations are available for this requirement.
                example: true
              mitigations:
                type: string
                description: Available mitigation description.
                example: Network segmentation limits exposure until the control is configured.
              poam_available:
                type: boolean
                description: Whether a plan of action and milestones applies.
                example: false
              poam:
                type: string
                description: Plan of action and milestones text.
                example: Remediation scheduled for the next maintenance window.
              severity_override_guidance:
                type: string
                description: Guidance for overriding the assigned severity.
                example: Severity may be lowered when the platform is not internet-facing.
              potential_impacts:
                type: string
                description: Potential operational impacts of applying the fix.
                example: Existing sessions may be terminated when authorization enforcement is enabled.
              third_party_tools:
                type: string
                description: Third-party tool considerations.
                example: None.
              mitigation_control:
                type: string
                description: Mitigation control description.
                example: Compensating access controls at the ingress proxy.
              responsibility:
                type: string
                description: Responsible party for the requirement.
                example: System Administrator
              ia_controls:
                type: string
                description: Legacy IA control mapping.
                example: ECAN-1
              _destroy:
                type: boolean
                description: Send true to delete this DISA description record.
                example: false
    RuleCreateResponse:
      description: 'Response from requirement creation. Toast notification plus the created row under the "data" key, shaped by the parent component''s document_type: a STIG rule or an authored SRG requirement.'
      type: object
      required:
        - toast
        - data
      properties:
        toast:
          $ref: '#/components/schemas/ToastObject'
        data:
          oneOf:
            - $ref: '#/components/schemas/RuleEditorResponse'
            - $ref: '#/components/schemas/AuthoredSrgRuleEditorResponse'
    RulePickerResponse:
      description: Lightweight rule for picker dropdowns. Serialized by RuleBlueprint :picker view. Extends RuleSummary defaults with displayed_name and satisfaction relationships. 13 total fields.
      allOf:
        - $ref: '#/components/schemas/RuleSummary'
        - type: object
          required:
            - displayed_name
            - satisfies
            - satisfied_by
          properties:
            displayed_name:
              type: string
              description: Human-readable rule identifier (PREFIX-rule_id).
              example: PHOS-03-000001
            satisfies:
              type: array
              description: Rules that this rule satisfies.
              items:
                $ref: '#/components/schemas/SatisfactionSummary'
            satisfied_by:
              type: array
              description: Rules that satisfy this rule.
              items:
                $ref: '#/components/schemas/SatisfiedBySummary'
    AuthoredSrgRulePickerResponse:
      description: 'A component-authored SRG requirement for picker dropdowns. Serialized by AuthoredSrgRuleBlueprint :picker view — the default requirement fields plus displayed_name. Carries no satisfaction relationships: authored requirements have no satisfies graph, and the keys are omitted entirely rather than sent empty. The closed property set keeps this shape disjoint from RulePickerResponse wherever the two appear in a oneOf.'
      type: object
      required:
        - id
        - rule_id
        - locked
        - displayed_name
      additionalProperties: false
      properties:
        id:
          type: integer
          description: Unique requirement identifier.
          example: 10633
        rule_id:
          type: string
          description: Six-digit zero-padded requirement number combined with the component prefix to form the displayed ID.
          example: '000001'
        title:
          type:
            - string
            - 'null'
          description: Title of the requirement. Null if not yet authored.
          example: The container platform must enforce approved authorizations.
        version:
          type:
            - string
            - 'null'
          description: Requirement version identifier. Minted at release for authored requirements.
          example: null
        status:
          type:
            - string
            - 'null'
          description: Applicability status in the SRG authoring vocabulary.
          enum:
            - Not Yet Determined
            - Applicable
            - Not Applicable
            - null
          example: Applicable
        rule_severity:
          type:
            - string
            - 'null'
          description: DISA severity level.
          enum:
            - low
            - medium
            - high
            - null
          example: medium
        locked:
          type: boolean
          description: Whether the requirement is locked from further edits.
          example: false
        review_requestor_id:
          type:
            - integer
            - 'null'
          description: ID of the user who requested review. Null if no review requested.
          example: null
        changes_requested:
          type: boolean
          description: Whether changes have been requested during review.
          example: false
        comment_summary:
          type: object
          description: Per-requirement comment counts.
          required:
            - open
            - total
          additionalProperties: false
          properties:
            open:
              type: integer
              description: Comments not yet adjudicated, including replies under open parents.
              example: 0
            total:
              type: integer
              description: Total comment count across all statuses.
              example: 0
        displayed_name:
          type: string
          description: Component-scoped display name (PREFIX-rule_id).
          example: RCPK-00-000001
    RuleBasicFields:
      description: Lightweight rule snapshot used for version-to-version diff comparisons. Produced by Rule#basic_fields. Contains only the fields needed to detect content changes between component releases.
      type: object
      required:
        - rule_id
      additionalProperties: false
      properties:
        rule_id:
          type: string
          description: Six-digit zero-padded rule number.
          example: '000001'
        title:
          type:
            - string
            - 'null'
          description: Rule title.
          example: The operating system must provide automated mechanisms for account management.
        vuln_discussion:
          type:
            - string
            - 'null'
          description: Vulnerability discussion text (from first DISA rule description).
          example: Without automated mechanisms, account management becomes error-prone...
        check:
          type:
            - string
            - 'null'
          description: Check procedure text (exported format from checks).
          example: Verify the operating system provides automated mechanisms...
        fix:
          type:
            - string
            - 'null'
          description: Fix text (exported format).
          example: Configure the operating system to provide automated mechanisms...
    HistoryChangeEntry:
      description: 'A single rule change between two component versions. Part of the component version history diff. The change type determines which fields are present: ''added'' has only diff, ''removed'' has only base, ''updated'' has both.'
      type: object
      required:
        - change
      additionalProperties: false
      properties:
        change:
          type: string
          description: Type of change between versions.
          enum:
            - added
            - removed
            - updated
          example: updated
        base:
          description: Rule content in the previous version. Present for 'removed' and 'updated' changes.
          $ref: '#/components/schemas/RuleBasicFields'
        diff:
          description: Rule content in the new version. Present for 'added' and 'updated' changes.
          $ref: '#/components/schemas/RuleBasicFields'
    ComponentReleaseResponse:
      type: object
      description: 'Successful release of an SRG component. The component''s requirements are published to the SRG catalog: the catalog entry is shaped exactly like an uploaded SRG (other components can base on it immediately, and is-latest tracks it), and the changelog summarizes what left the document via executed relocations.'
      required:
        - toast
        - catalog_srg
        - changelog
      properties:
        toast:
          $ref: '#/components/schemas/ToastObject'
        catalog_srg:
          type: object
          description: The catalog entry created by this release.
          required:
            - id
            - srg_id
            - version
            - name
          properties:
            id:
              type: integer
              description: Catalog SecurityRequirementsGuide record id.
            srg_id:
              type: string
              description: The released document's benchmark id — the component name with spaces underscored.
              example: Container_Best_Practice_SRG
            version:
              type: string
              description: Catalog version string mapped from the component's integer version/release pair.
              example: V1R1
            name:
              type: string
              description: Display name composed from the benchmark id and version.
              example: Container Best Practice SRG - Ver 1, Rel 1
        changelog:
          type: object
          description: Release changelog content. Removals are the component's executed relocation records — requirements that left this document for another SRG. Storage is not implied; this is the generated content.
          required:
            - version
            - removals
            - text
          properties:
            version:
              type: string
              example: V1R1
            removals:
              type: array
              items:
                type: object
                required:
                  - identifier
                  - destination_abbreviation
                  - executed_on
                properties:
                  identifier:
                    type: string
                    description: The requirement's published identifier (its working display name if it was never minted).
                    example: SRG-OS-000811-CTR-000042
                  destination_abbreviation:
                    type: string
                    description: The destination SRG's short abbreviation.
                    example: WEB
                  executed_on:
                    type: string
                    format: date
                    example: '2026-07-20'
            text:
              type: string
              description: Plain-text rendering of the changelog.
    RuleSectionLockResponse:
      description: Response from section lock/unlock. Toast notification plus the updated rule. Used by PATCH /rules/:id/section_locks and PATCH /rules/:id/bulk_section_locks.
      type: object
      required:
        - rule
        - toast
      properties:
        rule:
          $ref: '#/components/schemas/RuleEditorResponse'
        toast:
          $ref: '#/components/schemas/ToastObject'
    RequirementRelocationSummary:
      description: 'A relocation proposal in the per-SRG backlog: the open proposal plus enough source identity to act on it, or a retained non-concur whose rationale communicates the refusal back to the source author. Executed relocation records are immutable history and never served here.'
      type: object
      additionalProperties: false
      required:
        - id
        - source_rule_id
        - target_technology_token
        - source_displayed_name
        - component_id
      properties:
        id:
          type: integer
          description: Relocation record identifier.
          example: 12
        source_rule_id:
          type: integer
          description: The authored requirement proposed for relocation.
          example: 5137
        target_technology_token:
          type: string
          description: The destination SRG's abbreviation (CTR, GPOS, DB — the SRG-world analogue of a STIG component prefix).
          example: CTR
        created_at:
          type: string
          description: When the marker was created. Blueprinter format.
          example: 2026-07-20 15:36:06 UTC
        source_displayed_name:
          type: string
          description: Component-prefixed requirement name of the source.
          example: CNTR-00-000051
        component_id:
          type: integer
          description: The source requirement's component.
          example: 42
        component_name:
          type:
            - string
            - 'null'
          description: Name of the source component.
          example: Container Platform SRG
        requested_by_name:
          type:
            - string
            - 'null'
          description: Who marked the requirement. Null when the account was removed.
          example: Jane Doe
        declined_at:
          type:
            - string
            - 'null'
          description: When the receiving side declined the proposal. Null while the proposal is open. Blueprinter format.
          example: null
        adjudication_rationale:
          type:
            - string
            - 'null'
          description: Why the proposal was declined — required on every decline, shown to the source author. Null while the proposal is open.
          example: null
        declined_by_name:
          type:
            - string
            - 'null'
          description: Who declined the proposal. Null while open or when the account was removed.
          example: null
    RelocationDestination:
      description: 'A destination SRG option for the relocation propose flow: one row per SRG abbreviation across the SRG components the caller can discover. An open (unreleased) component wins the row; released true marks the queued next-release case (proposals wait until the receiving admin creates the next version).'
      type: object
      additionalProperties: false
      required:
        - token
        - name
        - released
      properties:
        token:
          type: string
          description: The destination SRG's abbreviation — the short code its requirement IDs start with (CTR, GPOS, DB).
          example: CTR
        name:
          type: string
          description: The component carrying the abbreviation — the open component when one exists, otherwise the latest released one.
          example: Container Platform SRG
        released:
          type: boolean
          description: True when no open component carries the abbreviation — a proposal queues for the SRG's next release.
          example: false
    RequirementRelocationDryRun:
      description: 'Zero-write preview of executing a relocation: whether the move can run, every reason it cannot, and exactly what would be created and tombstoned.'
      type: object
      additionalProperties: false
      required:
        - valid
        - errors
        - source_displayed_name
        - would_create
        - would_tombstone_source
      properties:
        valid:
          type: boolean
          description: Whether execute would run with these inputs.
          example: true
        errors:
          type: array
          description: Every reason the move cannot run. Empty when valid.
          items:
            type: string
          example: []
        source_displayed_name:
          type: string
          description: Component-prefixed name of the source requirement.
          example: CNTR-00-000051
        target_component_id:
          type:
            - integer
            - 'null'
          description: The destination component.
          example: 42
        target_component_name:
          type:
            - string
            - 'null'
          description: Name of the destination component.
          example: Container Platform SRG
        would_create:
          type: object
          additionalProperties: false
          description: The requirement that execute would create in the target.
          properties:
            title:
              type:
                - string
                - 'null'
              description: Title carried over from the source.
              example: The application must enforce approved authorizations
            status:
              type:
                - string
                - 'null'
              description: Authoring status carried over from the source.
              example: Applicable
            rule_id:
              type:
                - string
                - 'null'
              description: The number the moved requirement would land under — the next in the target component's own sequence (working numbers are local ordinals; identifiers mint at release).
              example: '000003'
            derived_from_srg_rule_id:
              type:
                - integer
                - 'null'
              description: Core-requirement lineage preserved on the move.
              example: 5137
        would_tombstone_source:
          type: boolean
          description: Execute soft-deletes the source row — always true.
          example: true
    RequirementRelocationAcceptResponse:
      description: 'Combined response for accepting a relocation proposal: the success toast plus the id of the requirement that landed in the destination component, so the editor can materialize the new row without a page reload.'
      type: object
      required:
        - toast
        - landed_rule_id
      additionalProperties: false
      properties:
        toast:
          $ref: '#/components/schemas/ToastObject'
        landed_rule_id:
          type: integer
          description: The requirement created in the destination component.
          example: 5137
    ReviewInput:
      description: Input fields for creating a review action (comment, approval, review request, or lock) on a rule.
      type: object
      required:
        - action
        - comment
      properties:
        action:
          type: string
          description: The review action to perform. Vocabulary and role tiers come from Review::ACTION_PERMISSIONS — comment (viewer+), request_review / revoke_review_request (author+), request_changes / approve (reviewer+), lock_control / unlock_control (admin).
          enum:
            - comment
            - request_review
            - revoke_review_request
            - request_changes
            - approve
            - lock_control
            - unlock_control
          example: comment
        comment:
          type: string
          description: The comment text accompanying this review action.
          example: The fix text should reference the container runtime configuration file.
        section:
          type:
            - string
            - 'null'
          description: Rule section this comment applies to (e.g. fixtext, check_content, vuln_discussion). Null for general comments; component-level comments are always stored section-less.
          example: fixtext
        responding_to_review_id:
          type: integer
          description: ID of the parent review when creating a threaded reply.
          example: 43
    BulkTriageResponse:
      description: Response from bulk-triaging multiple reviews at once. Returns the updated reviews and any auto-created response reviews (one per triaged comment if a response_comment was provided).
      type: object
      required:
        - reviews
        - response_reviews
      additionalProperties: false
      properties:
        reviews:
          type: array
          description: The triaged reviews with updated triage_status.
          items:
            $ref: '#/components/schemas/ReviewSummary'
        response_reviews:
          type: array
          description: Auto-created response reviews (one per triaged comment when response_comment is provided). Empty array if no response_comment.
          items:
            $ref: '#/components/schemas/ReviewSummary'
    MergeResponse:
      description: Response from merging duplicate reviews into a single survivor. The survivor absorbs the merged content. Duplicates are marked triage_status=duplicate with duplicate_of_review_id pointing to the survivor.
      type: object
      required:
        - survivor
        - duplicates
      additionalProperties: false
      properties:
        survivor:
          description: The surviving review that absorbed the duplicates.
          $ref: '#/components/schemas/ReviewSummary'
        duplicates:
          type: array
          description: The merged reviews, now marked as duplicates of the survivor.
          items:
            $ref: '#/components/schemas/ReviewSummary'
    ReviewUpdateInput:
      description: Input for editing an existing comment's text. Editing is allowed only while the comment is still pending triage; lifecycle fields (action, section, triage status) are server-controlled and cannot be changed here.
      type: object
      required:
        - comment
      additionalProperties: false
      properties:
        comment:
          type: string
          description: The replacement comment text.
          example: The fix text should reference the container runtime configuration file.
    ReviewWrapper:
      description: 'Standard review mutation response. Wraps a single ReviewSummary in a { review: ... } envelope. Used by update, reopen, withdraw, admin_withdraw, admin_restore, move_to_rule, and section endpoints.'
      type: object
      required:
        - review
      additionalProperties: false
      properties:
        review:
          $ref: '#/components/schemas/ReviewSummary'
    TriageResponse:
      description: Response from triage or adjudicate actions. Contains the updated review and an optional response_review (child comment created atomically when the triager provides a response comment alongside the triage decision).
      type: object
      required:
        - review
      additionalProperties: false
      properties:
        review:
          $ref: '#/components/schemas/ReviewSummary'
        response_review:
          description: Child review created as a response to the decision. Null if no response text was provided. Present when the request includes response_comment (triage) or resolution_comment (adjudicate).
          oneOf:
            - $ref: '#/components/schemas/ReviewSummary'
            - type: 'null'
    AdminDestroyResponse:
      description: Response from admin hard-delete of a review. Returns null for the review (confirming deletion) and the destroyed review's ID for frontend cleanup.
      type: object
      required:
        - review
        - destroyed_id
      additionalProperties: false
      properties:
        review:
          type: 'null'
          description: Always null — confirms the review was destroyed.
          example: null
        destroyed_id:
          type: integer
          description: ID of the destroyed review, for frontend to remove from local state.
          example: 44
    ReactionsSummary:
      description: Detailed reaction lists showing which users reacted with each type.
      type: object
      required:
        - up
        - down
      additionalProperties: false
      properties:
        up:
          type: array
          description: Users who upvoted.
          items:
            type: object
            required:
              - name
            properties:
              name:
                type: string
                description: Display name of the user who upvoted.
                example: Jane Doe
        down:
          type: array
          description: Users who downvoted.
          items:
            type: object
            required:
              - name
            properties:
              name:
                type: string
                description: Display name of the user who downvoted.
                example: John Smith
    ReactionToggleResponse:
      description: Response after toggling a reaction on a comment, returning updated aggregate counts and the current user's reaction.
      type: object
      required:
        - reactions
      additionalProperties: false
      properties:
        reactions:
          type: object
          description: Updated reaction state for the comment.
          required:
            - up
            - down
            - mine
          properties:
            up:
              type: integer
              description: Total number of upvote reactions.
              example: 4
            down:
              type: integer
              description: Total number of downvote reactions.
              example: 1
            mine:
              type:
                - string
                - 'null'
              description: The current user's reaction type (up or down). Null if the user has no active reaction.
              example: up
    MembershipInput:
      description: Input fields for creating or updating a project or component membership.
      type: object
      required:
        - user_id
        - role
      properties:
        user_id:
          type: integer
          description: ID of the user to add or update membership for.
          example: 42
        membership_id:
          type: integer
          description: ID of the existing membership record to update. Omit when creating a new membership.
          example: 15
        membership_type:
          type: string
          description: Whether this membership is for a project or a component.
          enum:
            - Project
            - Component
          example: Project
        role:
          type: string
          description: Access role granted to the user (viewer < author < reviewer < admin).
          enum:
            - viewer
            - author
            - reviewer
            - admin
          example: author
    MembershipUpdateInput:
      description: Update for an existing membership — the membership is addressed by the URL and only the role may change; user and membership type are fixed at creation.
      type: object
      required:
        - role
      properties:
        role:
          type: string
          description: Access role granted to the user (viewer < author < reviewer < admin).
          enum:
            - viewer
            - author
            - reviewer
            - admin
          example: reviewer
    SrgSummary:
      description: Security Requirements Guide (SRG) listing entry. Serialized by SrgBlueprint (default/index view). SRGs use release_date (not benchmark_date — that is STIGs).
      type: object
      required:
        - id
        - srg_id
        - severity_counts
        - core
      properties:
        id:
          type: integer
          description: Unique SRG identifier.
          example: 1
        srg_id:
          type:
            - string
            - 'null'
          description: DISA SRG identifier string.
          example: Container_Platform_SRG
        core:
          type: boolean
          description: Whether this is a core SRG. Core SRGs are the raw material SRG-kind components derive from; derived (non-core) SRGs are the valid sources for STIG-kind components. Drives the creation-flow source picker's eligibility filtering.
          example: false
        name:
          type:
            - string
            - 'null'
          description: Short name including version and release.
          example: Container Platform SRG - Ver 2, Rel 4
        title:
          type:
            - string
            - 'null'
          description: Full official title.
          example: Container Platform Security Requirements Guide
        version:
          type:
            - string
            - 'null'
          description: Version string (e.g. V2R4).
          example: V2R4
        release_date:
          type:
            - string
            - 'null'
          format: date
          description: Date the SRG was published by DISA.
          example: '2025-10-28'
        is_latest:
          type: boolean
          description: Whether this is the latest available release of this SRG.
          example: true
        latest_available_version:
          type:
            - string
            - 'null'
          description: Version string of this SRG's latest available release. Null when is_latest=true.
          example: null
        latest_available_id:
          type:
            - integer
            - 'null'
          description: ID of the latest available SRG for linking. Null when is_latest=true.
          example: null
        severity_counts:
          type: object
          description: Count of rules at each severity level within this SRG.
          properties:
            high:
              type: integer
              example: 8
            medium:
              type: integer
              example: 177
            low:
              type: integer
              example: 3
    SrgDetailResponse:
      description: Full SRG detail with nested rules. Serialized by SrgBlueprint :show view. Extends SrgSummary with srg_rules array.
      allOf:
        - $ref: '#/components/schemas/SrgSummary'
        - type: object
          properties:
            srg_rules:
              type: array
              description: All rules in this SRG, each with full DISA metadata and check content.
              items:
                $ref: '#/components/schemas/SrgRuleSummary'
    StigSummary:
      description: Security Technical Implementation Guide (STIG) listing entry. Serialized by StigBlueprint (default/index view). STIGs use benchmark_date (not release_date — that is SRGs).
      type: object
      required:
        - id
        - stig_id
        - severity_counts
      properties:
        id:
          type: integer
          description: Unique STIG identifier.
          example: 1
        stig_id:
          type:
            - string
            - 'null'
          description: DISA STIG identifier string.
          example: Application_Security_Development_STIG
        name:
          type:
            - string
            - 'null'
          description: Short name including version and release.
          example: Application Security Development STIG - Ver 6, Rel 4
        title:
          type:
            - string
            - 'null'
          description: Full official title.
          example: Application Security and Development Security Technical Implementation Guide
        version:
          type:
            - string
            - 'null'
          description: Version string (e.g. V6R4).
          example: V6R4
        benchmark_date:
          type:
            - string
            - 'null'
          format: date
          description: Date the STIG benchmark was published by DISA.
          example: '2025-10-01'
        is_latest:
          type: boolean
          description: Whether this is the latest available release of this STIG.
          example: true
        latest_available_version:
          type:
            - string
            - 'null'
          description: Version string of this STIG's latest available release. Null when is_latest=true.
          example: null
        latest_available_id:
          type:
            - integer
            - 'null'
          description: ID of the latest available STIG for linking. Null when is_latest=true.
          example: null
        severity_counts:
          type: object
          description: Count of rules at each severity level within this STIG.
          properties:
            high:
              type: integer
              example: 34
            medium:
              type: integer
              example: 230
            low:
              type: integer
              example: 22
    StigRuleSummary:
      description: Published STIG rule — a finalized security requirement from a published STIG.
      type: object
      required:
        - id
        - rule_id
        - title
      additionalProperties: false
      properties:
        id:
          type: integer
          description: Unique STIG rule identifier.
          example: 600
        rule_id:
          type: string
          description: DISA rule identifier.
          example: SV-222396r857506_rule
        title:
          type: string
          description: Requirement title.
          example: The container platform must enforce approved authorizations for access.
        version:
          type:
            - string
            - 'null'
          description: STIG version identifier.
          example: CNTR-00-000050
        rule_severity:
          type:
            - string
            - 'null'
          enum:
            - low
            - medium
            - high
          example: medium
        rule_weight:
          type:
            - string
            - 'null'
          example: '10.0'
        ident:
          type:
            - string
            - 'null'
          description: CCI identifiers (comma-separated).
          example: CCI-000213
        ident_system:
          type:
            - string
            - 'null'
          example: http://cyber.mil/cci
        fixtext:
          type:
            - string
            - 'null'
          description: Fix text describing how to remediate.
          example: Configure the container platform to enforce approved authorizations...
        fixtext_fixref:
          type:
            - string
            - 'null'
          example: F-25073r857505_fix
        fix_id:
          type:
            - string
            - 'null'
          example: F-25073r857505_fix
        srg_id:
          type:
            - string
            - 'null'
          description: SRG requirement this STIG rule implements.
          example: SRG-APP-000033-CTR-000095
        vuln_id:
          type:
            - string
            - 'null'
          description: Vulnerability identifier (V-number).
          example: V-222396
        legacy_ids:
          type:
            - string
            - 'null'
          description: Legacy rule identifiers from previous STIG versions (comma-separated string).
          example: V-69239, SV-83861
        vendor_comments:
          type:
            - string
            - 'null'
          description: Vendor-provided implementation guidance or additional context.
          example: This is addressed by the vendor's default configuration.
        disa_rule_descriptions_attributes:
          type: array
          description: DISA rule description records.
          items:
            $ref: '#/components/schemas/DisaRuleDescription'
        checks_attributes:
          type: array
          description: Check content records.
          items:
            $ref: '#/components/schemas/CheckSummary'
    StigDetailResponse:
      description: Full STIG detail with nested rules. Serialized by StigBlueprint :show view. Extends StigSummary with description and stig_rules array.
      allOf:
        - $ref: '#/components/schemas/StigSummary'
        - type: object
          properties:
            description:
              type:
                - string
                - 'null'
              description: Full description of the STIG.
              example: This Security Technical Implementation Guide is published as a tool to improve the security of Department of Defense information systems.
            stig_rules:
              type: array
              description: All rules in this STIG, each with full DISA metadata and check content.
              items:
                $ref: '#/components/schemas/StigRuleSummary'
    IdentitySummary:
      description: A linked external identity (provider + uid) for a user account.
      type: object
      required:
        - provider
        - title
      properties:
        id:
          type: integer
          description: Unique identity record identifier.
          example: 7
        provider:
          type: string
          description: Registry key of the authentication provider (e.g. okta, login_gov, oidc, ldap, github).
          example: okta
        email:
          type:
            - string
            - 'null'
          format: email
          description: Email address asserted by this provider. For display/audit only — not the link key.
          example: jane@example.com
        title:
          type: string
          description: Human-facing display name for the provider, from the OIDC registry or titleized fallback.
          example: Okta
        last_sign_in_at:
          type:
            - string
            - 'null'
          description: ISO 8601 timestamp of the most recent sign-in via this identity. Null if never used.
          example: '2026-06-14T13:29:15Z'
        can_unlink:
          type: boolean
          description: Whether this identity can be unlinked (false when it is the user's only sign-in method). Only present in the profile view.
          example: true
    UserSummary:
      description: Summary of a user account for admin management views.
      type: object
      required:
        - id
        - email
        - admin
      additionalProperties: false
      properties:
        id:
          type: integer
          description: Unique user identifier.
          example: 42
        name:
          type:
            - string
            - 'null'
          description: Display name chosen by the user. Null if not yet set.
          example: Jane Doe
        email:
          type: string
          format: email
          description: Login email address.
          example: jane.doe@example.org
        provider:
          type:
            - string
            - 'null'
          description: Authentication provider used for this account (local, github, ldap, or oidc). Null for local accounts.
          example: local
        admin:
          type: boolean
          description: Whether the user has administrator privileges.
          example: false
        last_sign_in_at:
          type:
            - string
            - 'null'
          description: Timestamp of the most recent successful sign-in. Null if the user has never signed in. Blueprinter format.
          example: 2026-05-28 15:00:00 UTC
        failed_attempts:
          type: integer
          description: Number of consecutive failed login attempts since last successful sign-in.
          example: 0
        locked_at:
          type:
            - string
            - 'null'
          description: Timestamp when the account was locked due to excessive failed attempts. Null if not locked. Blueprinter format.
          example: null
        slack_user_id:
          type:
            - string
            - 'null'
          description: Slack member ID driving this user's Slack notifications. Settable by the user (profile) and by admins (create/update, audited). Null when not set.
          example: U0123456789
        identities:
          type: array
          description: Linked external identities for this user (provider, email, last sign-in). Present in admin and profile views.
          items:
            $ref: '#/components/schemas/IdentitySummary'
    UserToastResponse:
      description: Combined response containing a toast notification and the updated user. Used by PUT /users/:id, POST /users/:id/lock, POST /users/:id/unlock.
      type: object
      required:
        - toast
        - user
      additionalProperties: false
      properties:
        toast:
          $ref: '#/components/schemas/ToastObject'
        user:
          $ref: '#/components/schemas/UserSummary'
    UserCommentRow:
      description: A single comment row in the user's My Comments listing. Serialized by UsersController#comment_row_for — different shape from CommentRow (component triage listing). Includes project/component context for cross-project navigation but omits triage attribution fields.
      type: object
      required:
        - id
        - commentable_type
        - comment
        - created_at
        - triage_status
        - responses_count
        - reactions
      additionalProperties: false
      properties:
        id:
          type: integer
          description: Unique review/comment identifier.
          example: 29
        project_id:
          type:
            - integer
            - 'null'
          description: ID of the project containing this comment's rule/component.
          example: 3
        project_name:
          type:
            - string
            - 'null'
          description: Name of the project.
          example: vSphere 7.0
        component_id:
          type:
            - integer
            - 'null'
          description: ID of the component containing the rule.
          example: 4
        component_name:
          type:
            - string
            - 'null'
          description: Name of the component.
          example: Photon OS 3
        author_name:
          type:
            - string
            - 'null'
          description: Display name of the comment author.
          example: Jane Doe
        rule_id:
          type:
            - integer
            - 'null'
          description: ID of the rule this comment is attached to. Null for component-level comments.
          example: 2397
        rule_displayed_name:
          type:
            - string
            - 'null'
          description: Human-readable rule identifier (PREFIX-rule_id). "(component)" for component-level comments.
          example: PHOS-03-000039
        commentable_type:
          type: string
          description: Polymorphic type indicating whether the comment targets a rule or a component.
          enum:
            - BaseRule
            - Component
          example: BaseRule
        section:
          type:
            - string
            - 'null'
          description: Rule section the comment applies to.
          example: fixtext
        comment:
          type: string
          description: The comment text content.
          example: 'vSphere 7.0: fix command targets ESXi 6.7 path — should reference 7.0 layout.'
        created_at:
          type: string
          description: Timestamp when the comment was created. Blueprinter format.
          example: 2026-05-19 14:08:18 UTC
        triage_status:
          type:
            - string
            - 'null'
          description: Current triage disposition of the comment.
          enum:
            - pending
            - concur
            - concur_with_comment
            - non_concur
            - duplicate
            - informational
            - needs_clarification
            - withdrawn
            - addressed_by
            - null
          example: pending
        triage_set_at:
          type:
            - string
            - 'null'
          description: Timestamp when the triage status was last changed.
          example: null
        adjudicated_at:
          type:
            - string
            - 'null'
          description: Timestamp when the comment reached a terminal triage state.
          example: null
        duplicate_of_review_id:
          type:
            - integer
            - 'null'
          description: ID of the review this is marked as a duplicate of.
          example: null
        addressed_by_rule_id:
          type:
            - integer
            - 'null'
          description: ID of the rule that addresses this comment.
          example: null
        srg_info:
          type:
            - object
            - 'null'
          description: SRG baseline metadata for the comment's component.
          properties:
            title:
              type: string
              description: Full title of the SRG.
              example: General Purpose Operating System Security Requirements Guide
            version:
              type: string
              description: SRG version in DISA V{major}R{minor} format.
              example: V3R3
            is_latest:
              type: boolean
              description: Whether this SRG version is the latest available.
              example: true
        parent_rule_displayed_name:
          type:
            - string
            - 'null'
          description: Display name of the parent rule if this rule is a child via satisfies. Null if no parent.
          example: null
        addressed_by_rule_name:
          type:
            - string
            - 'null'
          description: Display name of the rule that addresses this comment.
          example: null
        latest_activity_at:
          type:
            - string
            - 'null'
          description: Most recent activity timestamp (triage, adjudication, or reply).
          example: null
        responses_count:
          type: integer
          description: Number of threaded replies to this comment.
          example: 0
        reactions:
          type: object
          description: Aggregate reaction counts with current user's reaction.
          additionalProperties: false
          required:
            - up
            - down
          properties:
            up:
              type: integer
              description: Number of upvote reactions.
              example: 0
            down:
              type: integer
              description: Number of downvote reactions.
              example: 0
            mine:
              type:
                - string
                - 'null'
              description: The current user's reaction type. Null if no active reaction.
              example: null
    AdminCreateResponse:
      description: Response from admin user creation. Success includes toast, user, and optional reset_url. Error includes only the toast (no user key).
      type: object
      required:
        - toast
      properties:
        toast:
          $ref: '#/components/schemas/ToastObject'
        user:
          description: The created user (present on success, absent on error).
          $ref: '#/components/schemas/UserSummary'
        reset_url:
          type: string
          format: uri
          description: One-time password reset URL. Present only when SMTP is unavailable and no password was provided — the admin must deliver this link manually.
          example: https://vulcan.example.org/users/password/edit?reset_password_token=abc123def456
    ResetLinkResponse:
      description: Response containing a toast notification and a one-time password reset URL for admin-generated resets.
      type: object
      required:
        - toast
        - reset_url
      additionalProperties: false
      properties:
        toast:
          $ref: '#/components/schemas/ToastObject'
        reset_url:
          type: string
          format: uri
          description: One-time password reset URL to share with the user.
          example: https://vulcan.example.org/users/password/edit?reset_password_token=abc123def456
    AccessRequestToastResponse:
      description: Response from creating or deleting a project access request. The id is the affected access request — newly created on POST, destroyed on DELETE.
      type: object
      required:
        - toast
        - id
      additionalProperties: false
      properties:
        toast:
          $ref: '#/components/schemas/ToastObject'
        id:
          type: integer
          description: ID of the affected access request.
          example: 42
    TriageResponseTemplate:
      description: A reusable response template for triage decisions, scoped to a project.
      type: object
      required:
        - id
        - name
        - body
      properties:
        id:
          type: integer
          description: Unique template identifier.
          example: 1
        name:
          type: string
          description: Short name displayed in the template picker dropdown.
          example: Accept - standard
        body:
          type: string
          description: Markdown response text inserted into the triage response field.
          example: Concur with the finding as written. No changes needed.
        created_by_id:
          type:
            - integer
            - 'null'
          description: ID of the user who created this template.
          example: 42
        created_at:
          type: string
          format: date-time
          description: When the template was created.
          example: '2026-06-03T01:00:00Z'
    PersonalAccessTokenSummary:
      description: A personal access token as seen by its OWNER (GET /personal_access_tokens). Carries no owner identity — administrators listing another user's tokens get PersonalAccessTokenAdminSummary instead. The secret itself is returned only once, at creation, and is never retrievable here.
      type: object
      required:
        - id
        - name
        - token_prefix
        - scopes
        - created_at
      additionalProperties: false
      properties:
        id:
          type: integer
          description: Unique token identifier.
          example: 1
        name:
          type: string
          description: Human-readable label the owner gave the token.
          example: CI Pipeline
        token_prefix:
          type: string
          example: vulcan_a
          description: First 8 characters of the token for identification. The full token is never stored or returned after creation.
        scopes:
          type: array
          description: Capabilities granted to the token.
          items:
            type: string
            enum:
              - read
              - write
              - admin
          example:
            - read
            - write
        expires_at:
          type:
            - string
            - 'null'
          example: '2026-08-30'
          description: ISO 8601 date. Maximum 365 days from creation. Null means no expiry (warned in UI).
        last_used_at:
          type:
            - string
            - 'null'
          example: 2026-05-30 14:22:01 UTC
          description: Timestamp of last API request using this token. Updated at most once per minute.
        revoked_at:
          type:
            - string
            - 'null'
          example: null
          description: Null when active. Timestamp when revoked (soft-delete preserves audit trail).
        allowed_ips:
          type:
            - array
            - 'null'
          items:
            type: string
          example:
            - 10.0.0.0/8
            - 192.168.1.0/24
          description: CIDR allowlist. Null or empty means any IP allowed.
        created_at:
          type: string
          description: When the token was created. Blueprinter format.
          example: 2026-05-30 10:00:00 UTC
    PersonalAccessTokenAdminSummary:
      description: 'A personal access token as seen by an administrator listing ANOTHER user''s tokens (GET /personal_access_tokens?user_id=). Adds the owner''s identity to the standard summary so oversight is attributable. Read-only oversight: the secret is never included here or anywhere after creation, and no endpoint lets an administrator mint a token on this owner''s account.'
      type: object
      required:
        - id
        - name
        - token_prefix
        - scopes
        - created_at
        - user_id
      additionalProperties: false
      properties:
        id:
          type: integer
          description: Unique token identifier.
          example: 1
        name:
          type: string
          description: Human-readable label the owner gave the token.
          example: CI Pipeline
        token_prefix:
          type: string
          description: First 8 characters of the token for identification. The full token is never stored or returned after creation.
          example: vulcan_a
        scopes:
          type: array
          description: Capabilities granted to the token.
          items:
            type: string
            enum:
              - read
              - write
              - admin
          example:
            - read
            - write
        expires_at:
          type:
            - string
            - 'null'
          description: ISO 8601 date. Maximum 365 days from creation. Null means no expiry (warned in UI).
          example: '2026-08-30'
        last_used_at:
          type:
            - string
            - 'null'
          description: Timestamp of last API request using this token. Updated at most once per minute.
          example: 2026-05-30 14:22:01 UTC
        revoked_at:
          type:
            - string
            - 'null'
          description: Null when active. Timestamp when revoked (soft-delete preserves audit trail).
          example: null
        allowed_ips:
          type:
            - array
            - 'null'
          description: CIDR allowlist. Null or empty means any IP allowed.
          items:
            type: string
          example:
            - 10.0.0.0/8
            - 192.168.1.0/24
        created_at:
          type: string
          description: When the token was created. Blueprinter format.
          example: 2026-05-30 10:00:00 UTC
        user_id:
          type: integer
          description: Numeric id of the token's owner.
          example: 42
        user_name:
          type:
            - string
            - 'null'
          description: Display name of the token's owner. Null if the owner has not set one.
          example: Jane Doe
        user_email:
          type:
            - string
            - 'null'
          format: email
          description: Email address of the token's owner.
          example: jane@example.com
    PersonalAccessTokenCreateResponse:
      type: object
      required:
        - token
        - personal_access_token
      additionalProperties: false
      description: Returned only on token creation. The `token` field contains the raw token value — this is the ONLY time it is ever returned. After this response, only the SHA-256 digest is stored server-side.
      properties:
        token:
          type: string
          example: vulcan_a8BfDtSxd28fDXCuYaKxkeToqqJsmq5PekL6
          description: The raw API token. Show to user once, then discard. Never stored or returned again.
        personal_access_token:
          $ref: '#/components/schemas/PersonalAccessTokenSummary'
  responses:
    Unauthorized:
      description: Authentication required (RFC 9457 problem details). Either no API token and no signed-in session, or an Authorization header token that is revoked, expired, or mistyped. The `type` URI names which case applied and `how_to_authenticate` spells out both supported methods.
      content:
        application/problem+json:
          schema:
            type: object
            additionalProperties: false
            required:
              - type
              - title
              - status
              - detail
            properties:
              type:
                type: string
                description: Stable machine identifier for the error class — a URI reference anchoring into the live API docs.
                example: /docs/api/errors#not_authenticated
              title:
                type: string
                description: Short human summary of the error class.
                example: Not authenticated
              status:
                type: integer
                description: HTTP status code, repeated in the body.
                example: 401
              detail:
                type: string
                description: Occurrence-specific human explanation of why.
                example: This request included no API token and no valid signed-in session. If you were signed in, the session may have timed out, been signed out, or ended because this account signed in from another location.
              how_to_authenticate:
                type: object
                additionalProperties: false
                description: Both supported authentication methods (extension member).
                properties:
                  session:
                    type: string
                    description: How to authenticate with a browser session.
                    example: Sign in through the web UI (/users/sign_in) and retry with the session cookie.
                  token:
                    type: string
                    description: How to authenticate with a personal access token.
                    example: 'Create a personal access token (your profile page, or POST /personal_access_tokens) and send it in the request header: Authorization: Token <your-token>.'
          examples:
            not_authenticated:
              summary: No API token and no signed-in session
              value:
                type: /docs/api/errors#not_authenticated
                title: Not authenticated
                status: 401
                detail: This request included no API token and no valid signed-in session. If you were signed in, the session may have timed out, been signed out, or ended because this account signed in from another location.
                how_to_authenticate:
                  session: Sign in through the web UI (/users/sign_in) and retry with the session cookie.
                  token: 'Create a personal access token (your profile page, or POST /personal_access_tokens) and send it in the request header: Authorization: Token <your-token>.'
            invalid_token:
              summary: Authorization header token revoked, expired, or mistyped
              value:
                type: /docs/api/errors#invalid_token
                title: Invalid or expired API token
                status: 401
                detail: The Authorization header carried a token that does not match any active personal access token. It may be revoked, expired, or mistyped.
                how_to_authenticate:
                  session: Sign in through the web UI (/users/sign_in) and retry with the session cookie.
                  token: 'Create a personal access token (your profile page, or POST /personal_access_tokens) and send it in the request header: Authorization: Token <your-token>.'
            session_superseded:
              summary: Session ended because the account signed in from another location
              value:
                type: /docs/api/errors#session_superseded
                title: Session ended — signed in elsewhere
                status: 401
                detail: You were signed out because this account signed in from another location. Only one active session per account is allowed at a time.
                how_to_authenticate:
                  session: Sign in through the web UI (/users/sign_in) and retry with the session cookie.
                  token: 'Create a personal access token (your profile page, or POST /personal_access_tokens) and send it in the request header: Authorization: Token <your-token>.'
            session_timed_out:
              summary: Session expired after inactivity
              value:
                type: /docs/api/errors#session_timed_out
                title: Session timed out
                status: 401
                detail: Your session timed out after a period of inactivity. Sign in again to continue.
                how_to_authenticate:
                  session: Sign in through the web UI (/users/sign_in) and retry with the session cookie.
                  token: 'Create a personal access token (your profile page, or POST /personal_access_tokens) and send it in the request header: Authorization: Token <your-token>.'
    ErrorResponse:
      description: Catch-all 4XX error response. Domain and validation feedback arrives as the canonical toast object (application/json); auth and infrastructure errors (401/403/404/400) arrive as RFC 9457 problem details (application/problem+json) with a stable `type` URI, `title`, `status`, and `detail`.
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: string
                description: Human-readable error message (domain errors).
                example: No SRG IDs found in spreadsheet
              toast:
                type: object
                description: Optional toast notification with structured error details.
                properties:
                  title:
                    type: string
                    description: Short error summary.
                    example: Validation failed.
                  message:
                    type: array
                    description: Detail lines describing what went wrong.
                    items:
                      type: string
                    example:
                      - Name can't be blank.
                      - Prefix is too short (minimum is 2 characters).
                  variant:
                    type: string
                    description: Bootstrap alert variant (always danger for errors).
                    example: danger
          examples:
            validation_error:
              summary: Validation failure
              value:
                toast:
                  title: Validation failed.
                  message:
                    - Name can't be blank.
                  variant: danger
        application/problem+json:
          schema:
            type: object
            required:
              - type
              - title
              - status
              - detail
            properties:
              type:
                type: string
                description: Stable machine identifier for the error class — a URI reference anchoring into the live API docs.
                example: /docs/api/errors#not_found
              title:
                type: string
                description: Short human summary of the error class.
                example: Not found
              status:
                type: integer
                description: HTTP status code, repeated in the body.
                example: 404
              detail:
                type: string
                description: Occurrence-specific human explanation.
                example: The requested resource could not be found.
              how_to_authenticate:
                type: object
                description: Extension member on 401s — both authentication methods.
              admins:
                type: array
                description: Extension member on permission denials — who to ask.
                items:
                  type: object
              toast:
                type: object
                description: Legacy toast extension on permission denials.
          examples:
            not_found:
              summary: Resource not found
              value:
                type: /docs/api/errors#not_found
                title: Not found
                status: 404
                detail: The requested resource could not be found.
    NotFound:
      description: Resource not found (RFC 9457 problem details). The body is identical for a truly nonexistent resource and one the caller may not learn exists.
      content:
        application/problem+json:
          schema:
            type: object
            additionalProperties: false
            required:
              - type
              - title
              - status
              - detail
            properties:
              type:
                type: string
                description: Stable machine identifier for the error class — a URI reference anchoring into the live API docs.
                example: /docs/api/errors#not_found
              title:
                type: string
                description: Short human summary of the error class.
                example: Not found
              status:
                type: integer
                description: HTTP status code, repeated in the body.
                example: 404
              detail:
                type: string
                description: Occurrence-specific human explanation.
                example: The requested resource could not be found.
          examples:
            not_found:
              summary: No such resource is reachable by this request
              value:
                type: /docs/api/errors#not_found
                title: Not found
                status: 404
                detail: The requested resource could not be found.
    Forbidden:
      description: Insufficient permissions (RFC 9457 problem details). The `type` URI names the denial class; permission denials carry the project `admins` to ask for access, plus a legacy `toast` extension kept until every consumer reads the problem fields directly.
      content:
        application/problem+json:
          schema:
            type: object
            additionalProperties: false
            required:
              - type
              - title
              - status
              - detail
            properties:
              type:
                type: string
                description: Stable machine identifier for the error class — a URI reference anchoring into the live API docs.
                example: /docs/api/errors#permission_denied
              title:
                type: string
                description: Short human summary of the error class.
                example: Permission denied
              status:
                type: integer
                description: HTTP status code, repeated in the body.
                example: 403
              detail:
                type: string
                description: Occurrence-specific human explanation of why.
                example: You are not authorized to perform viewer actions on this project
              admins:
                type: array
                description: Project admin contacts to ask for access (extension member, present on permission denials when a project or component is in scope; empty otherwise).
                items:
                  type: object
                  additionalProperties: false
                  required:
                    - name
                    - email
                  properties:
                    name:
                      type: string
                      description: Admin display name.
                      example: Alice Admin
                    email:
                      type: string
                      format: email
                      description: Admin contact email.
                      example: alice@example.com
              toast:
                type: object
                additionalProperties: false
                description: Legacy toast extension for older consumers (permission denials only).
                properties:
                  title:
                    type: string
                    description: Toast heading.
                    example: Not Authorized.
                  message:
                    type: array
                    description: Toast body lines.
                    items:
                      type: string
                    example:
                      - You are not authorized to perform viewer actions on this project
                  variant:
                    type: string
                    description: Bootstrap alert variant.
                    example: danger
          examples:
            permission_denied:
              summary: Authenticated but lacking the required project capability
              value:
                type: /docs/api/errors#permission_denied
                title: Permission denied
                status: 403
                detail: You are not authorized to perform viewer actions on this project
                admins:
                  - name: Alice Admin
                    email: alice@example.com
                toast:
                  title: Not Authorized.
                  message:
                    - You are not authorized to perform viewer actions on this project
                  variant: danger
            insufficient_token_scope:
              summary: Token lacks the scope this request requires
              value:
                type: /docs/api/errors#insufficient_token_scope
                title: Insufficient token scope
                status: 403
                detail: This request requires the write scope, and the token does not grant it.
            ip_not_allowed:
              summary: Request IP outside the token allowlist
              value:
                type: /docs/api/errors#ip_not_allowed
                title: IP address not allowed
                status: 403
                detail: The request came from an IP address outside this token's allowlist.
    UnprocessableEntity:
      description: Validation errors prevented the request from being processed.
      content:
        application/json:
          schema:
            type: object
            properties:
              toast:
                type: object
                description: Toast notification containing validation error details.
                properties:
                  title:
                    type: string
                    description: Short error summary.
                    example: Unable to save changes.
                  message:
                    type: array
                    description: Specific validation error messages.
                    items:
                      type: string
                    example:
                      - Name can't be blank.
                      - Prefix format is invalid.
                  variant:
                    type: string
                    description: Bootstrap alert variant (always danger for validation errors).
                    example: danger
          examples:
            validation_failure:
              summary: Component creation with missing fields
              value:
                toast:
                  title: Unable to save changes.
                  message:
                    - Name can't be blank.
                    - Prefix format is invalid.
                  variant: danger
  parameters:
    SearchQuery:
      name: q
      in: query
      required: true
      description: Search query string (minimum 2 characters).
      schema:
        type: string
        minLength: 2
      example: container platform
    ProjectId:
      name: projectId
      in: path
      required: true
      description: Numeric ID of the target project.
      schema:
        type: integer
      example: 7
    TriageStatusFilter:
      name: triage_status
      in: query
      description: Filter comments by triage disposition. Defaults to "pending" — the triage table opens on undispositioned comments. Use "all" to return comments in any status.
      schema:
        type: string
        enum:
          - all
          - pending
          - concur
          - concur_with_comment
          - non_concur
          - duplicate
          - informational
          - needs_clarification
          - withdrawn
          - addressed_by
        default: pending
      example: pending
    PageParam:
      name: page
      in: query
      description: Page number for paginated results (1-based).
      schema:
        type: integer
        minimum: 1
        default: 1
      example: 1
    PerPageParam:
      name: per_page
      in: query
      description: Number of items to return per page.
      schema:
        type: integer
        minimum: 1
        maximum: 1000
        default: 25
      example: 25
    ComponentId:
      name: componentId
      in: path
      required: true
      description: Numeric ID of the target component.
      schema:
        type: integer
      example: 29
    RuleId:
      name: ruleId
      in: path
      required: true
      description: Numeric ID of the target rule.
      schema:
        type: integer
      example: 100
    ReviewId:
      name: reviewId
      in: path
      required: true
      description: Numeric ID of the target review.
      schema:
        type: integer
      example: 44
    MembershipId:
      name: membershipId
      in: path
      required: true
      description: Numeric ID of the target membership record.
      schema:
        type: integer
      example: 15
    SrgId:
      name: id
      in: path
      required: true
      description: Numeric ID of the target Security Requirements Guide (SRG).
      schema:
        type: integer
      example: 1
    StigId:
      name: id
      in: path
      required: true
      description: Numeric ID of the target Security Technical Implementation Guide (STIG).
      schema:
        type: integer
      example: 1
    UserId:
      name: userId
      in: path
      required: true
      description: Numeric ID of the target user.
      schema:
        type: integer
      example: 42
