{
  "openapi": "3.0.0",
  "info": {
    "title": "Bedrock API",
    "description": "Bedrock API for managing immutable ledger records, certificates, and document verification",
    "version": "1.0.0",
    "contact": {
      "name": "Bedrock Support",
      "url": "https://bedrockcompliance.co.uk"
    }
  },
  "servers": [
    {
      "url": "http://localhost:4000",
      "description": "Local development server"
    },
    {
      "url": "https://api.bedrockcompliance.co.uk",
      "description": "Production"
    },
    {
      "url": "https://api.staging.bedrockcompliance.co.uk",
      "description": "Staging"
    }
  ],
  "tags": [
    {
      "name": "Health",
      "description": "Health check and public key endpoints"
    },
    {
      "name": "Firm",
      "description": "Firm self-service endpoints for API keys, webhooks, users, and settings"
    },
    {
      "name": "Ledger",
      "description": "Immutable ledger operations for records and certificates"
    },
    {
      "name": "Principal",
      "description": "Principal operations for submitting and tracking review jobs"
    },
    {
      "name": "Public",
      "description": "Public endpoints requiring no authentication"
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "tags": [
          "Health"
        ],
        "summary": "Health check",
        "description": "Check API health status",
        "operationId": "healthCheck",
        "responses": {
          "200": {
            "description": "API is healthy",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "ok"
                    },
                    "timestamp": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/signing-key": {
      "get": {
        "tags": [
          "Health"
        ],
        "summary": "Get the public signing key",
        "description": "Returns the public signing key used to sign ledger chain hashes",
        "operationId": "getSigningKey",
        "responses": {
          "200": {
            "description": "Public key in base64",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "publicKey": {
                      "type": "string",
                      "description": "Base64-encoded public key"
                    },
                    "algorithm": {
                      "type": "string",
                      "example": "Ed25519"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/firm/me": {
      "get": {
        "tags": [
          "Firm"
        ],
        "summary": "Get current firm details",
        "operationId": "getCurrentFirm",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Current firm details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "frnNumber": {
                      "type": "string"
                    },
                    "plan": {
                      "type": "string",
                      "enum": [
                        "LEDGER",
                        "PRINCIPAL",
                        "BOTH"
                      ]
                    },
                    "enforceImpactAssessments": {
                      "type": "boolean",
                      "description": "Whether the firm requires an approved impact assessment for any job that declares a modelProvider/modelVersion. Defaults to true; can be toggled via PATCH /v1/firm/me/settings."
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/firm/me/settings": {
      "patch": {
        "tags": [
          "Firm"
        ],
        "summary": "Update firm settings",
        "description": "Toggle firm-level controls. Currently supports the impact-assessment enforcement gate; more settings will be added here rather than scattered across new endpoints. Compliance-relevant changes emit a FIRM_SETTINGS_UPDATED ledger event carrying the field-level diff and the authenticated actor (user or API key). No-op writes (where the incoming value already matches the stored value) are ignored — nothing is written to the database and no ledger record is emitted.",
        "operationId": "updateFirmSettings",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "enforceImpactAssessments": {
                    "type": "boolean",
                    "description": "When true (the default), jobs submitted with a modelProvider/modelVersion are gated on a matching APPROVED impact assessment."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated firm settings",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": { "type": "string" },
                    "name": { "type": "string" },
                    "frnNumber": { "type": "string" },
                    "plan": { "type": "string" },
                    "enforceImpactAssessments": { "type": "boolean" },
                    "createdAt": { "type": "string", "format": "date-time" }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/firm/me/impact-assessments": {
      "get": {
        "tags": [
          "Firm"
        ],
        "summary": "List Consumer Duty impact assessments",
        "description": "Returns every impact assessment filed by the firm, newest first. Each entry carries the outcome responses, template version, model provider/version (if set), and the users who created and signed off the assessment.",
        "operationId": "listImpactAssessments",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "List of impact assessments",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ImpactAssessment"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "Firm"
        ],
        "summary": "File a draft impact assessment",
        "description": "Create a new DRAFT impact assessment for an AI use case. When outcomes are omitted, the draft is seeded with empty responses and a LOW risk rating for each of the four PRIN 2A outcome areas so the reviewer can fill them in progressively.",
        "operationId": "createImpactAssessment",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateImpactAssessmentParams"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Draft assessment created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImpactAssessment"
                }
              }
            }
          },
          "400": {
            "description": "useCase and description required"
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/firm/me/impact-assessments/{id}": {
      "get": {
        "tags": [
          "Firm"
        ],
        "summary": "Get an impact assessment",
        "operationId": "getImpactAssessment",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Impact assessment",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImpactAssessment"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Assessment not found"
          }
        }
      },
      "post": {
        "tags": [
          "Firm"
        ],
        "summary": "Update or transition an impact assessment",
        "description": "Edits the draft content and/or moves the assessment through its status graph: DRAFT → PENDING_SIGNOFF → APPROVED / REJECTED → SUPERSEDED. Approving requires an authenticated LEAD_REVIEWER or FIRM_ADMIN and produces an IMPACT_ASSESSMENT_APPROVED ledger event. APPROVED and SUPERSEDED assessments are immutable — further edits return 409 CONFLICT.",
        "operationId": "updateImpactAssessment",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateImpactAssessmentParams"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated impact assessment",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImpactAssessment"
                }
              }
            }
          },
          "400": {
            "description": "Invalid status value"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Senior sign-off required (LEAD_REVIEWER or FIRM_ADMIN) — or the caller is an API key (API keys cannot approve assessments)"
          },
          "404": {
            "description": "Assessment not found"
          },
          "409": {
            "description": "Invalid status transition, or an APPROVED / SUPERSEDED assessment was edited"
          }
        }
      }
    },
    "/v1/firm/me/impact-assessments/{id}/supersede": {
      "post": {
        "tags": [
          "Firm"
        ],
        "summary": "Mark an assessment as superseded",
        "description": "Retires an assessment in favour of a newer one covering the same use case. Superseded assessments are kept for audit trail but no longer count toward the impact-assessment enforcement gate.",
        "operationId": "supersedeImpactAssessment",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Assessment marked as superseded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImpactAssessment"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Assessment not found"
          }
        }
      }
    },
    "/v1/firm/me/stats": {
      "get": {
        "tags": [
          "Firm"
        ],
        "summary": "Get current firm statistics",
        "operationId": "getFirmStats",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Firm statistics",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "recordCount": {
                      "type": "integer"
                    },
                    "certificateCount": {
                      "type": "integer"
                    },
                    "jobCount": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/firm/me/backup-status": {
      "get": {
        "tags": [
          "Firm"
        ],
        "summary": "Get immutable backup status for the firm",
        "operationId": "getBackupStatus",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Backup status with record counts and spot-check results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "healthy": {
                      "type": "boolean"
                    },
                    "database": {
                      "type": "object",
                      "properties": {
                        "count": {
                          "type": "integer"
                        }
                      }
                    },
                    "immutableStorage": {
                      "type": "object",
                      "properties": {
                        "count": {
                          "type": "integer"
                        }
                      }
                    },
                    "countsMatch": {
                      "type": "boolean"
                    },
                    "latestRecord": {
                      "type": "object",
                      "nullable": true,
                      "properties": {
                        "sequenceNumber": {
                          "type": "integer"
                        },
                        "timestamp": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    },
                    "spotChecks": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "recordId": {
                            "type": "string"
                          },
                          "sequenceNumber": {
                            "type": "integer"
                          },
                          "match": {
                            "type": "boolean"
                          }
                        }
                      }
                    },
                    "checkedAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/firm/me/api-keys": {
      "get": {
        "tags": [
          "Firm"
        ],
        "summary": "List API keys for the authenticated firm",
        "operationId": "listApiKeys",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "List of API keys",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ApiKey"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "Firm"
        ],
        "summary": "Generate a new API key",
        "operationId": "generateApiKey",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Name/label for the API key"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "API key generated (shown only once)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "key": {
                      "type": "string",
                      "description": "The full API key \u2014 only returned at creation time"
                    },
                    "name": {
                      "type": "string"
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/firm/me/api-keys/{keyId}": {
      "delete": {
        "tags": [
          "Firm"
        ],
        "summary": "Revoke an API key",
        "operationId": "revokeApiKey",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "keyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "API key ID"
          }
        ],
        "responses": {
          "204": {
            "description": "API key revoked"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "API key not found"
          }
        }
      }
    },
    "/v1/firm/me/webhooks": {
      "get": {
        "tags": [
          "Firm"
        ],
        "summary": "List webhook endpoints for the authenticated firm",
        "operationId": "listWebhooks",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "List of webhook endpoints",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Webhook"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "Firm"
        ],
        "summary": "Register a webhook endpoint",
        "operationId": "createWebhook",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url",
                  "events"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Event types to subscribe to"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Webhook registered",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Webhook"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/firm/me/webhooks/{id}": {
      "delete": {
        "tags": [
          "Firm"
        ],
        "summary": "Deactivate a webhook endpoint",
        "operationId": "deleteWebhook",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Webhook ID"
          }
        ],
        "responses": {
          "204": {
            "description": "Webhook deactivated"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Webhook not found"
          }
        }
      }
    },
    "/v1/firm/me/models": {
      "get": {
        "tags": [
          "Firm"
        ],
        "summary": "List AI models tracked for the firm",
        "description": "Returns every distinct (provider, version) pair that has appeared on a review job, with usage counts and outcome rates. The registry is the entry point for AI accountability evidence \u2014 every advice record is tagged with the model that produced it.",
        "operationId": "listModels",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "List of model versions with usage stats",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ModelSummary"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/firm/me/models/drift": {
      "get": {
        "tags": [
          "Firm"
        ],
        "summary": "Detect drift across all AI models",
        "description": "Compares the trailing window against the prior baseline window for every (provider, version) the firm uses. Surfaces statistically significant changes in rejection rate, modification rate, and reviewer annotation frequency. Each model needs at least 5 completed jobs in both windows for a signal to fire.",
        "operationId": "getModelDriftReport",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "baselineDays",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 90,
              "minimum": 1,
              "maximum": 730
            },
            "description": "Length of the baseline window in days (defaults to 90)."
          },
          {
            "name": "currentDays",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 30,
              "minimum": 1,
              "maximum": 730
            },
            "description": "Length of the current window in days (defaults to 30)."
          }
        ],
        "responses": {
          "200": {
            "description": "Drift signals across all models",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelDriftReport"
                }
              }
            }
          },
          "400": {
            "description": "Invalid window parameters"
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/firm/me/models/{provider}/{version}/timeline": {
      "get": {
        "tags": [
          "Firm"
        ],
        "summary": "Weekly quality timeline for a model version",
        "description": "Returns weekly buckets of approval / modification / rejection rates and reviewer annotation counts for a specific model version. Use this to drill into a drift signal and see exactly when the regression started.",
        "operationId": "getModelTimeline",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "provider",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Model provider (e.g. \"openai\")"
          },
          {
            "name": "version",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Model version (e.g. \"gpt-4o-2024-08-06\")"
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Start of the window (defaults to 180 days ago)."
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "End of the window (defaults to now)."
          }
        ],
        "responses": {
          "200": {
            "description": "Weekly timeline points",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ModelTimelinePoint"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid date range"
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/firm/me/users": {
      "get": {
        "tags": [
          "Firm"
        ],
        "summary": "List users for the firm",
        "operationId": "listUsers",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "List of users",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/User"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "Firm"
        ],
        "summary": "Invite a user to the firm",
        "operationId": "inviteUser",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "email"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "fcaRef": {
                    "type": "string",
                    "description": "FCA individual reference number"
                  },
                  "qualifications": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "role": {
                    "type": "string",
                    "enum": [
                      "FIRM_ADMIN",
                      "REVIEWER",
                      "LEAD_REVIEWER"
                    ],
                    "default": "REVIEWER"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "User invited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/User"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/firm/me/users/{id}": {
      "delete": {
        "tags": [
          "Firm"
        ],
        "summary": "Deactivate a user",
        "operationId": "deactivateUser",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "User ID"
          }
        ],
        "responses": {
          "204": {
            "description": "User deactivated"
          },
          "400": {
            "description": "Cannot deactivate yourself"
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/ledger/records": {
      "get": {
        "tags": [
          "Ledger"
        ],
        "summary": "List ledger records with pagination and filters",
        "operationId": "listRecords",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 25
            }
          },
          {
            "name": "eventType",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "startDate",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "endDate",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "actorId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "documentHash",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "documentReference",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of ledger records",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Record"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "pageSize": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/ledger/records/{id}": {
      "get": {
        "tags": [
          "Ledger"
        ],
        "summary": "Get a single ledger record",
        "operationId": "getRecord",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Record ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Ledger record details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Record"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Record not found"
          }
        }
      }
    },
    "/v1/ledger/records/{id}/certificate": {
      "get": {
        "tags": [
          "Ledger"
        ],
        "summary": "Get presigned URL for certificate PDF",
        "operationId": "getRecordCertificate",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Record ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Presigned URL for certificate PDF",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Certificate not found"
          }
        }
      },
      "post": {
        "tags": [
          "Ledger"
        ],
        "summary": "Issue a certificate on demand for a ledger record",
        "description": "On-demand certificate issuance for any ledger record outside the auto-issue allowlist. Idempotent: returns the existing certificate id if one was already issued for the record.",
        "operationId": "issueRecordCertificate",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Record ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Certificate already existed or was enqueued for generation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "certificateId": {
                      "type": "string",
                      "nullable": true,
                      "description": "Existing certificate id, or null if cert-gen was enqueued"
                    },
                    "status": {
                      "type": "string",
                      "enum": ["existing", "enqueued"]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Ledger record not found"
          }
        }
      }
    },
    "/v1/ledger/certificates": {
      "get": {
        "tags": [
          "Ledger"
        ],
        "summary": "List all certificates for the authenticated firm",
        "operationId": "listCertificates",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "List of certificates",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Certificate"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/ledger/certificates/{id}": {
      "get": {
        "tags": [
          "Ledger"
        ],
        "summary": "Get certificate metadata by id",
        "description": "Item pair of `listCertificates` — returns the full Certificate envelope. For the PDF download, use `GET /v1/ledger/records/{id}/certificate` instead.",
        "operationId": "getCertificateById",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Certificate ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Certificate envelope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Certificate"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Certificate not found"
          }
        }
      }
    },
    "/v1/ledger/chains/verify": {
      "get": {
        "tags": [
          "Ledger"
        ],
        "summary": "Verify chain integrity for the authenticated firm",
        "operationId": "verifyChain",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Chain verification result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "valid": {
                      "type": "boolean"
                    },
                    "recordCount": {
                      "type": "integer"
                    },
                    "errors": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/verify/{certificateId}": {
      "get": {
        "tags": [
          "Public"
        ],
        "summary": "Verify a certificate (public endpoint)",
        "operationId": "verifyCertificate",
        "parameters": [
          {
            "name": "certificateId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Certificate ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Certificate verification result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "valid": {
                      "type": "boolean"
                    },
                    "certificate": {
                      "$ref": "#/components/schemas/Certificate"
                    },
                    "record": {
                      "type": "object",
                      "properties": {
                        "id": { "type": "string" },
                        "sequenceNumber": { "type": "integer" },
                        "documentHash": { "type": "string" },
                        "previousHash": { "type": "string" },
                        "recordHash": { "type": "string" },
                        "chainHash": { "type": "string" },
                        "signature": { "type": "string" },
                        "publicKey": { "type": "string" }
                      }
                    },
                    "verifiedAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "reason": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Certificate not found"
          }
        }
      }
    },
    "/v1/principal/jobs": {
      "post": {
        "tags": [
          "Principal"
        ],
        "summary": "Submit a document for review",
        "operationId": "submitJob",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "documentUrl",
                  "documentType",
                  "clientReference",
                  "documentReference",
                  "factFindSummary"
                ],
                "properties": {
                  "documentUrl": {
                    "type": "string",
                    "format": "uri",
                    "description": "URL to download the document"
                  },
                  "documentType": {
                    "type": "string",
                    "description": "Type of document (e.g. SUITABILITY_REPORT)"
                  },
                  "clientReference": {
                    "type": "string",
                    "description": "Client reference identifier"
                  },
                  "documentReference": {
                    "type": "string",
                    "description": "Document reference identifier"
                  },
                  "factFindSummary": {
                    "type": "object",
                    "description": "Summary of the client fact find"
                  },
                  "priority": {
                    "type": "string",
                    "description": "Job priority level",
                    "enum": [
                      "STANDARD",
                      "URGENT"
                    ]
                  },
                  "modelProvider": {
                    "type": "string",
                    "description": "AI model provider that produced the advice (e.g. \"openai\", \"anthropic\"). Required for inclusion in the model registry and drift detection.",
                    "example": "openai"
                  },
                  "modelVersion": {
                    "type": "string",
                    "description": "Specific model version that produced the advice. Pinning the exact version (rather than a moving alias) is what makes drift detection meaningful.",
                    "example": "gpt-4o-2024-08-06"
                  },
                  "modelConfiguration": {
                    "type": "object",
                    "description": "Inference parameters used for this generation (temperature, top_p, system prompt hash, etc.). Stored verbatim with the record so audits can reproduce the conditions.",
                    "additionalProperties": true,
                    "example": {
                      "temperature": 0.2,
                      "topP": 0.95,
                      "maxTokens": 2048
                    }
                  },
                  "vulnerabilityFlags": {
                    "type": "array",
                    "description": "FCA FG21/1 vulnerability drivers identified on the underlying client. Any non-empty value forces `requiresSeniorSignOff: true` and restricts routing to reviewers flagged as FG21/1 specialists.",
                    "items": {
                      "type": "string",
                      "enum": [
                        "health",
                        "life_event",
                        "capability",
                        "resilience"
                      ]
                    },
                    "example": [
                      "health",
                      "life_event"
                    ]
                  },
                  "requiresSeniorSignOff": {
                    "type": "boolean",
                    "description": "Explicitly require a LEAD_REVIEWER or FIRM_ADMIN to complete the job. Automatically forced to `true` when `vulnerabilityFlags` is non-empty \u2014 callers cannot opt out of senior sign-off on flagged cases.",
                    "default": false
                  },
                  "clientSegments": {
                    "type": "object",
                    "description": "Anonymised client segments used for bias / fairness monitoring (e.g. ageBand, riskProfile, productType). Values are plain strings and are aggregated across jobs on the `/v1/firm/me/bias` report \u2014 pick categorical labels rather than identifiers.",
                    "additionalProperties": {
                      "type": "string"
                    },
                    "example": {
                      "ageBand": "65+",
                      "riskProfile": "Cautious",
                      "productType": "SIPP"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Review job created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Plan insufficient for principal services"
          },
          "409": {
            "description": "IMPACT_ASSESSMENT_REQUIRED — the firm has the impact-assessment gate on (the default) and no approved assessment matches the modelProvider/modelVersion declared on this job. File one and have a senior sign off before retrying."
          },
          "422": {
            "description": "Document download failed"
          }
        }
      },
      "get": {
        "tags": [
          "Principal"
        ],
        "summary": "List review jobs with pagination",
        "operationId": "listJobs",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 25
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by job status"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of review jobs",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Job"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "pageSize": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/principal/jobs/{id}": {
      "get": {
        "tags": [
          "Principal"
        ],
        "summary": "Get review job details with anonymised review actions",
        "operationId": "getJob",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Job ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Review job details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Review job not found"
          }
        }
      }
    },
    "/v1/firm/me/incidents": {
      "get": {
        "tags": [
          "Firm"
        ],
        "summary": "List incidents for the firm",
        "description": "Returns the 200 most recent incidents for the authenticated firm, newest first. Each incident includes any remediation actions attached to it and the user who reported it.",
        "operationId": "listIncidents",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "List of incidents",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Incident"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Firm"
        ],
        "summary": "Log a new incident",
        "description": "Creates a new incident and enqueues an INCIDENT_LOGGED record to the immutable ledger. The incident snapshot (id, title, severity, category, affected jobs/records, reportedAt) is hashed and anchored to the ledger chain.",
        "operationId": "createIncident",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateIncidentParams"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Incident created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Incident"
                }
              }
            }
          },
          "400": {
            "description": "Missing title/description or invalid severity/category"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Firm not found"
          }
        }
      }
    },
    "/v1/firm/me/incidents/stats": {
      "get": {
        "tags": [
          "Firm"
        ],
        "summary": "Incident statistics and trends",
        "description": "Aggregate counts (total, open, resolved, critical), a breakdown by category, and a 12-month trend of incident volume. Open counts include OPEN, INVESTIGATING, and REMEDIATING states.",
        "operationId": "getIncidentStats",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Incident statistics",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IncidentStats"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          }
        }
      }
    },
    "/v1/firm/me/incidents/{id}": {
      "get": {
        "tags": [
          "Firm"
        ],
        "summary": "Get an incident with remediations",
        "description": "Returns a single incident scoped to the authenticated firm, with all attached remediation actions and the user who reported it.",
        "operationId": "getIncident",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Incident identifier"
          }
        ],
        "responses": {
          "200": {
            "description": "Incident with remediations",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Incident"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Incident not found"
          }
        }
      },
      "post": {
        "tags": [
          "Firm"
        ],
        "summary": "Update incident status or root cause",
        "description": "Updates an incident's status and/or root cause. Uses POST rather than PATCH for consistency with the rest of this namespace. When the status transitions to RESOLVED or CLOSED the server stamps resolvedAt and enqueues an INCIDENT_RESOLVED record to the immutable ledger.",
        "operationId": "updateIncident",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Incident identifier"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateIncidentParams"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated incident",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Incident"
                }
              }
            }
          },
          "400": {
            "description": "Invalid status"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Incident not found"
          }
        }
      }
    },
    "/v1/firm/me/incidents/{id}/remediations": {
      "post": {
        "tags": [
          "Firm"
        ],
        "summary": "Add a remediation action to an incident",
        "description": "Attach a new remediation action (with optional owner and due date) to an existing incident.",
        "operationId": "addIncidentRemediation",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Incident identifier"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateRemediationParams"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Remediation created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IncidentRemediation"
                }
              }
            }
          },
          "400": {
            "description": "Description required"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Incident not found"
          }
        }
      }
    },
    "/v1/firm/me/incidents/{id}/remediations/{remediationId}": {
      "post": {
        "tags": [
          "Firm"
        ],
        "summary": "Update a remediation action",
        "description": "Update the status of an existing remediation. completedAt is server-controlled: it is set when the status becomes COMPLETED and cleared otherwise.",
        "operationId": "updateIncidentRemediation",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Incident identifier"
          },
          {
            "name": "remediationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Remediation identifier"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateRemediationParams"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated remediation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IncidentRemediation"
                }
              }
            }
          },
          "400": {
            "description": "Invalid status"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Remediation not found"
          }
        }
      }
    },
    "/v1/firm/me/vulnerability": {
      "get": {
        "tags": [
          "Firm"
        ],
        "summary": "Vulnerability handling report (FG21/1)",
        "description": "Aggregated handling of FCA FG21/1 vulnerability drivers (health, life event, capability, financial resilience) across review jobs. Includes per-flag outcome breakdowns, counts of specialist reviewers and reviews awaiting senior sign-off, and a sample of recent flagged jobs.",
        "operationId": "getVulnerabilityReport",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Vulnerability report",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VulnerabilityReport"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          }
        }
      }
    },
    "/v1/firm/me/bias": {
      "get": {
        "tags": [
          "Firm"
        ],
        "summary": "Bias / fairness report across client segments",
        "description": "Splits completed review outcomes by client segment dimensions (age band, risk profile, product type, etc.) and flags segments whose rejection or modification rates diverge materially from the baseline or exceed an absolute rejection threshold. Flags come in two kinds: 'divergence' (rate differs from the dimension baseline) and 'absolute' (rate exceeds the configured threshold).",
        "operationId": "getBiasReport",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Start of reporting period (ISO date or datetime; defaults to 12 months ago). Date-only values are interpreted as the start of that UTC day."
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "End of reporting period (ISO date treated as end-of-day; defaults to now)."
          },
          {
            "name": "rejectionThreshold",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "format": "float",
              "minimum": 0,
              "maximum": 1,
              "default": 0.3
            },
            "description": "Absolute rejection-rate threshold for alerts, 0 to 1. Defaults to 0.3."
          }
        ],
        "responses": {
          "200": {
            "description": "Bias report",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BiasReport"
                }
              }
            }
          },
          "400": {
            "description": "Invalid date range or rejectionThreshold"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          }
        }
      }
    },
    "/v1/firm/me/reports/consumer-duty": {
      "get": {
        "tags": [
          "Firm"
        ],
        "summary": "Consumer Duty outcome metrics",
        "description": "Returns Consumer Duty outcome and control-evidence metrics for a reporting period: outcome breakdown, SLA compliance, reviewer workload, document-type breakdown, monthly outcome buckets, and a ledger crypto anchor covering the window. Includes the previous period's outcomes and SLA for comparison.",
        "operationId": "getConsumerDutyReport",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Start of reporting period. ISO date or datetime. Defaults to the start of the current quarter."
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "End of reporting period. ISO date treated as end-of-day. Defaults to today."
          }
        ],
        "responses": {
          "200": {
            "description": "Consumer Duty report",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConsumerDutyReportResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid date range"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          }
        }
      }
    },
    "/v1/firm/me/reports": {
      "get": {
        "tags": [
          "Firm"
        ],
        "summary": "List generated reports",
        "description": "Returns the audit log of previously-generated reports. Optionally filter by report type.",
        "operationId": "listReports",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/ReportType"
            },
            "description": "Filter by report type."
          }
        ],
        "responses": {
          "200": {
            "description": "List of report log entries",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Report"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid report type"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Firm"
        ],
        "summary": "Log a generated report",
        "description": "Record that a report was generated, for audit purposes. The server parses and normalises periodFrom/periodTo before persisting, and tags the entry with the authenticated user as the generator.",
        "operationId": "logReport",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateReportLogParams"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Report log entry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Report"
                }
              }
            }
          },
          "400": {
            "description": "Invalid report type or date range"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Firm": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "frnNumber": {
            "type": "string"
          },
          "plan": {
            "type": "string",
            "enum": [
              "LEDGER",
              "PRINCIPAL",
              "BOTH"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ApiKey": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "prefix": {
            "type": "string",
            "description": "First characters of the key for identification"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "revokedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "Webhook": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "firmId": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "deactivatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "User": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "fcaRef": {
            "type": "string",
            "nullable": true
          },
          "qualifications": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "role": {
            "type": "string",
            "enum": [
              "FIRM_ADMIN",
              "REVIEWER",
              "LEAD_REVIEWER"
            ]
          },
          "isAvailable": {
            "type": "boolean"
          },
          "invitedAt": {
            "type": "string",
            "format": "date-time"
          },
          "activatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "deactivatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "activeJobId": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "Record": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "firmId": {
            "type": "string"
          },
          "sequenceNumber": {
            "type": "integer"
          },
          "eventType": {
            "type": "string"
          },
          "actorId": {
            "type": "string"
          },
          "actorFcaRef": {
            "type": "string",
            "nullable": true
          },
          "actorName": {
            "type": "string"
          },
          "documentHash": {
            "type": "string"
          },
          "documentMetadata": {
            "type": "object"
          },
          "previousHash": {
            "type": "string",
            "nullable": true
          },
          "recordHash": {
            "type": "string"
          },
          "chainHash": {
            "type": "string"
          },
          "signature": {
            "type": "string"
          },
          "publicKey": {
            "type": "string"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time"
          },
          "reviewJobId": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "Certificate": {
        "type": "object",
        "description": "Thin envelope around a LedgerRecord. Cryptographic proof (hashes, signature, public key) lives on the linked record. Event-specific content captured at issue time lives in the metadata blob, projected by an event-typed assembler.",
        "properties": {
          "id": {
            "type": "string"
          },
          "ledgerRecordId": {
            "type": "string"
          },
          "firmId": {
            "type": "string"
          },
          "firmName": {
            "type": "string"
          },
          "firmFrnNumber": {
            "type": "string"
          },
          "issuedAt": {
            "type": "string",
            "format": "date-time"
          },
          "verificationUrl": {
            "type": "string",
            "format": "uri"
          },
          "pdfUrl": {
            "type": "string",
            "format": "uri"
          },
          "pdfStorageKey": {
            "type": "string"
          },
          "metadata": {
            "type": "object",
            "description": "Event-specific snapshot — shape depends on the linked record's eventType. Carries `eventLabel`, optional `statusPill`, and `sections` (heading + labelled fields)."
          }
        }
      },
      "Job": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "firmId": {
            "type": "string"
          },
          "documentType": {
            "type": "string"
          },
          "clientReference": {
            "type": "string"
          },
          "documentReference": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "QUEUED",
              "ASSIGNED",
              "IN_REVIEW",
              "COMPLETED"
            ]
          },
          "priority": {
            "type": "string",
            "enum": [
              "STANDARD",
              "URGENT"
            ]
          },
          "outcome": {
            "type": "string",
            "nullable": true,
            "enum": [
              "APPROVED",
              "REJECTED",
              "APPROVED_WITH_MODIFICATIONS"
            ]
          },
          "assignedTo": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "modelProvider": {
            "type": "string",
            "nullable": true,
            "description": "AI model provider that produced the underlying advice."
          },
          "modelVersion": {
            "type": "string",
            "nullable": true,
            "description": "Specific model version that produced the underlying advice."
          },
          "modelConfiguration": {
            "type": "object",
            "default": {},
            "additionalProperties": true,
            "description": "Inference parameters used for this generation. Defaults to an empty object when the request omits the field \u2014 the column is non-null in the database."
          },
          "vulnerabilityFlags": {
            "type": "array",
            "description": "FCA FG21/1 vulnerability drivers recorded for this job. Flagged jobs are routed exclusively to specialist or lead reviewers and always require senior sign-off.",
            "items": {
              "type": "string",
              "enum": [
                "health",
                "life_event",
                "capability",
                "resilience"
              ]
            },
            "default": []
          },
          "requiresSeniorSignOff": {
            "type": "boolean",
            "description": "Whether this job must be completed by a LEAD_REVIEWER or FIRM_ADMIN. Automatically true for any job with `vulnerabilityFlags` populated.",
            "default": false
          },
          "clientSegments": {
            "type": "object",
            "default": {},
            "additionalProperties": {
              "type": "string"
            },
            "description": "Anonymised categorical segments used to power the `/v1/firm/me/bias` fairness monitor. Defaults to an empty object when the request omits the field."
          },
          "ledgerRecordId": {
            "type": "string",
            "nullable": true,
            "description": "Ledger record id for the outcome event (DOCUMENT_APPROVED / MODIFIED / REJECTED) once the review completes. Use as the key to fetch the certificate PDF via `GET /v1/ledger/records/{id}/certificate`."
          },
          "certificateId": {
            "type": "string",
            "nullable": true,
            "description": "Certificate id, populated once cert-gen finishes after the review completes. Use for verify deep-links."
          }
        }
      },
      "ModelSummary": {
        "type": "object",
        "description": "Aggregate stats for a single (provider, version) pair across all completed jobs the firm has submitted.",
        "properties": {
          "provider": {
            "type": "string",
            "example": "openai"
          },
          "version": {
            "type": "string",
            "example": "gpt-4o-2024-08-06"
          },
          "totalJobs": {
            "type": "integer",
            "description": "Total review jobs tagged with this model (any status)."
          },
          "approved": {
            "type": "integer"
          },
          "approvedWithModifications": {
            "type": "integer"
          },
          "rejected": {
            "type": "integer"
          },
          "pending": {
            "type": "integer",
            "description": "Jobs still in queue, assigned, or in review (no terminal outcome yet)."
          },
          "approvalRate": {
            "type": "number",
            "format": "float",
            "description": "Approved / completed (where completed = approved + modified + rejected)."
          },
          "modificationRate": {
            "type": "number",
            "format": "float"
          },
          "rejectionRate": {
            "type": "number",
            "format": "float"
          },
          "firstSeenAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastUsedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ModelDriftSignal": {
        "type": "object",
        "description": "A single drift signal for one (provider, version, metric) tuple.",
        "properties": {
          "provider": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "metric": {
            "type": "string",
            "enum": [
              "rejectionRate",
              "modificationRate",
              "annotationFrequency"
            ]
          },
          "current": {
            "type": "number",
            "format": "float",
            "description": "Value of the metric in the current window."
          },
          "baseline": {
            "type": "number",
            "format": "float",
            "description": "Value of the metric in the baseline window."
          },
          "delta": {
            "type": "number",
            "format": "float",
            "description": "Signed difference (current - baseline)."
          },
          "severity": {
            "type": "string",
            "enum": [
              "info",
              "warning",
              "alert"
            ]
          },
          "sampleSize": {
            "type": "integer",
            "description": "Number of completed jobs in the current window for this model."
          }
        }
      },
      "ModelDriftReport": {
        "type": "object",
        "properties": {
          "signals": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ModelDriftSignal"
            },
            "description": "All triggered drift signals, sorted by severity (alert > warning > info)."
          },
          "thresholds": {
            "type": "object",
            "properties": {
              "warning": {
                "type": "number",
                "format": "float",
                "description": "Absolute delta at which a warning fires (default 0.05 = 5%)."
              },
              "alert": {
                "type": "number",
                "format": "float",
                "description": "Absolute delta at which an alert fires (default 0.10 = 10%)."
              }
            }
          },
          "baselineWindowDays": {
            "type": "integer"
          },
          "currentWindowDays": {
            "type": "integer"
          }
        }
      },
      "ModelTimelinePoint": {
        "type": "object",
        "description": "Aggregate stats for one ISO week, used to plot per-model quality over time.",
        "properties": {
          "bucket": {
            "type": "string",
            "format": "date-time",
            "description": "ISO timestamp of the Monday that starts the week."
          },
          "total": {
            "type": "integer"
          },
          "approved": {
            "type": "integer"
          },
          "approvedWithModifications": {
            "type": "integer"
          },
          "rejected": {
            "type": "integer"
          },
          "approvalRate": {
            "type": "number",
            "format": "float"
          },
          "modificationRate": {
            "type": "number",
            "format": "float"
          },
          "rejectionRate": {
            "type": "number",
            "format": "float"
          },
          "annotationCount": {
            "type": "integer",
            "description": "Number of NOTE_ADDED reviewer actions on jobs completed in this week."
          }
        }
      },
      "ImpactAssessmentStatus": {
        "type": "string",
        "enum": [
          "DRAFT",
          "PENDING_SIGNOFF",
          "APPROVED",
          "REJECTED",
          "SUPERSEDED"
        ],
        "description": "Lifecycle state of a Consumer Duty impact assessment. APPROVED is immutable except via supersede. Invalid transitions return 409 CONFLICT."
      },
      "RiskRating": {
        "type": "string",
        "enum": [
          "LOW",
          "MEDIUM",
          "HIGH"
        ],
        "description": "Self-reported risk rating per PRIN 2A outcome within an impact assessment."
      },
      "OutcomeResponse": {
        "type": "object",
        "description": "A firm's response for one PRIN 2A outcome area inside an impact assessment.",
        "properties": {
          "responses": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Free-text answers keyed by the template question id (e.g. 'ps_target_market')."
          },
          "riskRating": {
            "$ref": "#/components/schemas/RiskRating"
          },
          "narrative": {
            "type": "string",
            "description": "Plain-English summary of the firm's position on this outcome."
          }
        },
        "required": [
          "responses",
          "riskRating",
          "narrative"
        ]
      },
      "ImpactAssessmentOutcomes": {
        "type": "object",
        "description": "Responses for all four PRIN 2A outcomes. Template v1 covers products & services, price & value, consumer understanding and consumer support.",
        "properties": {
          "products_services": { "$ref": "#/components/schemas/OutcomeResponse" },
          "price_value": { "$ref": "#/components/schemas/OutcomeResponse" },
          "consumer_understanding": { "$ref": "#/components/schemas/OutcomeResponse" },
          "consumer_support": { "$ref": "#/components/schemas/OutcomeResponse" }
        },
        "required": [
          "products_services",
          "price_value",
          "consumer_understanding",
          "consumer_support"
        ]
      },
      "ImpactAssessment": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "firmId": { "type": "string" },
          "useCase": {
            "type": "string",
            "description": "Short title for the AI use case (e.g. 'GPT-4 fact-find summariser')."
          },
          "description": {
            "type": "string",
            "description": "What the AI does and where in the workflow it runs."
          },
          "modelProvider": {
            "type": "string",
            "nullable": true,
            "description": "AI model provider this assessment covers (e.g. 'openai'). When set with modelVersion and the firm has enforceImpactAssessments on, jobs declaring the same provider/version are gated on this assessment reaching APPROVED."
          },
          "modelVersion": {
            "type": "string",
            "nullable": true,
            "description": "Specific model version this assessment covers (e.g. 'gpt-4o-2024-08-06')."
          },
          "templateVersion": {
            "type": "integer",
            "description": "Template schema version the outcomes conform to. v1 is the only template today."
          },
          "outcomes": {
            "$ref": "#/components/schemas/ImpactAssessmentOutcomes"
          },
          "status": {
            "$ref": "#/components/schemas/ImpactAssessmentStatus"
          },
          "createdById": {
            "type": "string",
            "nullable": true
          },
          "signedOffById": {
            "type": "string",
            "nullable": true
          },
          "signedOffAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Server-controlled. Stamped when the assessment transitions to APPROVED."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "createdBy": {
            "type": "object",
            "nullable": true,
            "properties": {
              "id": { "type": "string" },
              "name": { "type": "string" },
              "email": { "type": "string" }
            }
          },
          "signedOffBy": {
            "type": "object",
            "nullable": true,
            "properties": {
              "id": { "type": "string" },
              "name": { "type": "string" },
              "email": { "type": "string" }
            }
          },
          "ledgerRecordId": {
            "type": "string",
            "nullable": true,
            "description": "Ledger record this assessment is anchored to once approved. Null before approval. Use as the key to fetch the certificate PDF via `GET /v1/ledger/records/{id}/certificate`."
          },
          "certificateId": {
            "type": "string",
            "nullable": true,
            "description": "Certificate id, populated once cert-gen finishes after approval. Use for verify deep-links."
          }
        },
        "required": [
          "id",
          "firmId",
          "useCase",
          "description",
          "templateVersion",
          "outcomes",
          "status",
          "createdAt",
          "updatedAt"
        ]
      },
      "CreateImpactAssessmentParams": {
        "type": "object",
        "properties": {
          "useCase": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "modelProvider": {
            "type": "string",
            "nullable": true,
            "description": "Optional. Pair with modelVersion to tie this assessment to a specific model and feed the enforcement gate on job submission."
          },
          "modelVersion": {
            "type": "string",
            "nullable": true
          },
          "outcomes": {
            "$ref": "#/components/schemas/ImpactAssessmentOutcomes",
            "description": "Optional. When omitted, the draft is seeded with empty responses and a LOW risk rating per outcome."
          }
        },
        "required": [
          "useCase",
          "description"
        ]
      },
      "UpdateImpactAssessmentParams": {
        "type": "object",
        "description": "Partial update for a draft/pending assessment and/or a status transition. APPROVED and SUPERSEDED assessments accept status-only transitions (SUPERSEDED) — any content edit returns 409 CONFLICT. Approving transitions require an authenticated LEAD_REVIEWER or FIRM_ADMIN.",
        "properties": {
          "useCase": { "type": "string" },
          "description": { "type": "string" },
          "modelProvider": {
            "type": "string",
            "nullable": true
          },
          "modelVersion": {
            "type": "string",
            "nullable": true
          },
          "outcomes": {
            "$ref": "#/components/schemas/ImpactAssessmentOutcomes"
          },
          "status": {
            "$ref": "#/components/schemas/ImpactAssessmentStatus"
          }
        }
      },
      "IncidentSeverity": {
        "type": "string",
        "enum": [
          "LOW",
          "MEDIUM",
          "HIGH",
          "CRITICAL"
        ]
      },
      "IncidentCategory": {
        "type": "string",
        "enum": [
          "MODEL_FAILURE",
          "BAD_ADVICE",
          "DATA_ISSUE",
          "SLA_BREACH",
          "SECURITY",
          "OTHER"
        ]
      },
      "IncidentStatus": {
        "type": "string",
        "enum": [
          "OPEN",
          "INVESTIGATING",
          "REMEDIATING",
          "RESOLVED",
          "CLOSED"
        ]
      },
      "RemediationStatus": {
        "type": "string",
        "enum": [
          "PENDING",
          "IN_PROGRESS",
          "COMPLETED",
          "CANCELLED"
        ]
      },
      "IncidentRemediation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "incidentId": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/RemediationStatus"
          },
          "ownerName": {
            "type": "string",
            "nullable": true
          },
          "dueAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "completedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Server-controlled: set when status becomes COMPLETED, cleared otherwise.",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "incidentId",
          "description",
          "status",
          "createdAt"
        ]
      },
      "Incident": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "firmId": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "severity": {
            "$ref": "#/components/schemas/IncidentSeverity"
          },
          "category": {
            "$ref": "#/components/schemas/IncidentCategory"
          },
          "status": {
            "$ref": "#/components/schemas/IncidentStatus"
          },
          "reportedAt": {
            "type": "string",
            "format": "date-time"
          },
          "reportedById": {
            "type": "string",
            "nullable": true
          },
          "resolvedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Server-controlled. Stamped when status transitions to RESOLVED or CLOSED.",
            "nullable": true
          },
          "rootCause": {
            "type": "string",
            "nullable": true
          },
          "affectedReviewJobIds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Review job IDs affected by the incident."
          },
          "affectedLedgerRecordIds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Ledger record IDs affected by the incident."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          },
          "remediations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/IncidentRemediation"
            },
            "description": "Remediation actions, present when the incident is fetched individually."
          },
          "reportedBy": {
            "type": "object",
            "nullable": true,
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "email": {
                "type": "string"
              }
            }
          }
        },
        "required": [
          "id",
          "firmId",
          "title",
          "description",
          "severity",
          "category",
          "status",
          "reportedAt",
          "affectedReviewJobIds",
          "affectedLedgerRecordIds",
          "metadata"
        ]
      },
      "IncidentStats": {
        "type": "object",
        "properties": {
          "total": {
            "type": "integer"
          },
          "open": {
            "type": "integer",
            "description": "Incidents in OPEN, INVESTIGATING, or REMEDIATING state."
          },
          "resolved": {
            "type": "integer",
            "description": "Incidents in RESOLVED or CLOSED state."
          },
          "critical": {
            "type": "integer",
            "description": "Incidents with severity CRITICAL (any status)."
          },
          "byCategory": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            },
            "description": "Count of incidents per category."
          },
          "trend": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "month": {
                  "type": "string",
                  "description": "YYYY-MM bucket in UTC."
                },
                "count": {
                  "type": "integer"
                }
              },
              "required": [
                "month",
                "count"
              ]
            },
            "description": "Trailing 12 months of incident volume, oldest first."
          }
        },
        "required": [
          "total",
          "open",
          "resolved",
          "critical",
          "byCategory",
          "trend"
        ]
      },
      "CreateIncidentParams": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "severity": {
            "$ref": "#/components/schemas/IncidentSeverity"
          },
          "category": {
            "$ref": "#/components/schemas/IncidentCategory"
          },
          "affectedReviewJobIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "affectedLedgerRecordIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "title",
          "description",
          "severity",
          "category"
        ]
      },
      "UpdateIncidentParams": {
        "type": "object",
        "properties": {
          "status": {
            "$ref": "#/components/schemas/IncidentStatus"
          },
          "rootCause": {
            "type": "string",
            "description": "Pass null to clear a previously-set root cause. resolvedAt is server-controlled and cannot be set via this endpoint.",
            "nullable": true
          }
        }
      },
      "CreateRemediationParams": {
        "type": "object",
        "properties": {
          "description": {
            "type": "string"
          },
          "ownerName": {
            "type": "string"
          },
          "dueAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "description"
        ]
      },
      "UpdateRemediationParams": {
        "type": "object",
        "properties": {
          "status": {
            "$ref": "#/components/schemas/RemediationStatus"
          }
        }
      },
      "VulnerabilityFlag": {
        "type": "string",
        "enum": [
          "health",
          "life_event",
          "capability",
          "resilience"
        ],
        "description": "FCA FG21/1 vulnerability driver."
      },
      "VulnerabilityFlagBreakdown": {
        "type": "object",
        "properties": {
          "flag": {
            "$ref": "#/components/schemas/VulnerabilityFlag"
          },
          "total": {
            "type": "integer"
          },
          "approved": {
            "type": "integer"
          },
          "approvedWithModifications": {
            "type": "integer"
          },
          "rejected": {
            "type": "integer"
          },
          "pending": {
            "type": "integer"
          },
          "approvalRate": {
            "type": "number",
            "format": "float"
          },
          "modificationRate": {
            "type": "number",
            "format": "float"
          },
          "rejectionRate": {
            "type": "number",
            "format": "float"
          }
        },
        "required": [
          "flag",
          "total",
          "approved",
          "approvedWithModifications",
          "rejected",
          "pending",
          "approvalRate",
          "modificationRate",
          "rejectionRate"
        ]
      },
      "VulnerabilityReport": {
        "type": "object",
        "properties": {
          "totalFlaggedJobs": {
            "type": "integer"
          },
          "totalUnflaggedJobs": {
            "type": "integer"
          },
          "flagBreakdown": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VulnerabilityFlagBreakdown"
            }
          },
          "specialistReviewerCount": {
            "type": "integer",
            "description": "Reviewers trained to handle vulnerable clients."
          },
          "totalReviewerCount": {
            "type": "integer"
          },
          "awaitingSeniorSignOff": {
            "type": "integer",
            "description": "Flagged jobs still awaiting senior reviewer sign-off."
          },
          "recentFlaggedJobs": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "documentReference": {
                  "type": "string"
                },
                "documentType": {
                  "type": "string"
                },
                "flags": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/VulnerabilityFlag"
                  }
                },
                "status": {
                  "type": "string"
                },
                "submittedAt": {
                  "type": "string",
                  "format": "date-time"
                },
                "completedAt": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true
                },
                "outcome": {
                  "type": "string",
                  "nullable": true
                }
              },
              "required": [
                "id",
                "documentReference",
                "documentType",
                "flags",
                "status",
                "submittedAt"
              ]
            }
          }
        },
        "required": [
          "totalFlaggedJobs",
          "totalUnflaggedJobs",
          "flagBreakdown",
          "specialistReviewerCount",
          "totalReviewerCount",
          "awaitingSeniorSignOff",
          "recentFlaggedJobs"
        ]
      },
      "SegmentOutcome": {
        "type": "object",
        "properties": {
          "segment": {
            "type": "string",
            "description": "Segment dimension key (e.g. ageBand, riskProfile, productType)."
          },
          "value": {
            "type": "string",
            "description": "Segment value within the dimension."
          },
          "total": {
            "type": "integer"
          },
          "approved": {
            "type": "integer"
          },
          "approvedWithModifications": {
            "type": "integer"
          },
          "rejected": {
            "type": "integer"
          },
          "approvalRate": {
            "type": "number",
            "format": "float"
          },
          "modificationRate": {
            "type": "number",
            "format": "float"
          },
          "rejectionRate": {
            "type": "number",
            "format": "float"
          }
        },
        "required": [
          "segment",
          "value",
          "total",
          "approved",
          "approvedWithModifications",
          "rejected",
          "approvalRate",
          "modificationRate",
          "rejectionRate"
        ]
      },
      "BiasFlag": {
        "type": "object",
        "properties": {
          "segment": {
            "type": "string"
          },
          "value": {
            "type": "string"
          },
          "metric": {
            "type": "string",
            "enum": [
              "rejectionRate",
              "modificationRate"
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "divergence",
              "absolute"
            ],
            "description": "'divergence' \u2014 rate differs materially from the dimension baseline; 'absolute' \u2014 rate exceeds the configured absolute rejection threshold."
          },
          "observed": {
            "type": "number",
            "format": "float"
          },
          "baseline": {
            "type": "number",
            "format": "float"
          },
          "delta": {
            "type": "number",
            "format": "float"
          },
          "severity": {
            "type": "string",
            "enum": [
              "info",
              "warning",
              "alert"
            ]
          },
          "sampleSize": {
            "type": "integer"
          }
        },
        "required": [
          "segment",
          "value",
          "metric",
          "kind",
          "observed",
          "baseline",
          "delta",
          "severity",
          "sampleSize"
        ]
      },
      "SegmentDimension": {
        "type": "object",
        "properties": {
          "segment": {
            "type": "string"
          },
          "values": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SegmentOutcome"
            }
          },
          "baselineRejectionRate": {
            "type": "number",
            "format": "float"
          },
          "flags": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BiasFlag"
            }
          }
        },
        "required": [
          "segment",
          "values",
          "baselineRejectionRate",
          "flags"
        ]
      },
      "BiasTrendPoint": {
        "type": "object",
        "properties": {
          "bucket": {
            "type": "string",
            "description": "Time bucket identifier (e.g. YYYY-MM)."
          },
          "segment": {
            "type": "string"
          },
          "value": {
            "type": "string"
          },
          "total": {
            "type": "integer"
          },
          "rejectionRate": {
            "type": "number",
            "format": "float"
          }
        },
        "required": [
          "bucket",
          "segment",
          "value",
          "total",
          "rejectionRate"
        ]
      },
      "BiasReport": {
        "type": "object",
        "properties": {
          "periodFrom": {
            "type": "string",
            "format": "date-time"
          },
          "periodTo": {
            "type": "string",
            "format": "date-time",
            "description": "Inclusive end of the reporting period (23:59:59.999 UTC for date-only inputs)."
          },
          "totalRecords": {
            "type": "integer"
          },
          "overallRejectionRate": {
            "type": "number",
            "format": "float",
            "description": "Overall rejection rate across every completed job in the period, regardless of which segment dimensions each job populates."
          },
          "dimensions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SegmentDimension"
            }
          },
          "flags": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BiasFlag"
            },
            "description": "All flags raised, aggregated across dimensions."
          },
          "thresholds": {
            "type": "object",
            "description": "Divergence thresholds, absolute delta from baseline.",
            "properties": {
              "warning": {
                "type": "number",
                "format": "float"
              },
              "alert": {
                "type": "number",
                "format": "float"
              }
            },
            "required": [
              "warning",
              "alert"
            ]
          },
          "rejectionAlertThreshold": {
            "type": "number",
            "format": "float",
            "description": "Absolute rejection-rate threshold used to raise 'absolute' flags."
          },
          "trend": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BiasTrendPoint"
            }
          }
        },
        "required": [
          "periodFrom",
          "periodTo",
          "totalRecords",
          "overallRejectionRate",
          "dimensions",
          "flags",
          "thresholds",
          "rejectionAlertThreshold",
          "trend"
        ]
      },
      "ReportType": {
        "type": "string",
        "enum": [
          "CONSUMER_DUTY_OUTCOME"
        ],
        "description": "Generated report type. Only CONSUMER_DUTY_OUTCOME is currently supported."
      },
      "OutcomeBreakdown": {
        "type": "object",
        "properties": {
          "total": {
            "type": "integer"
          },
          "approved": {
            "type": "integer"
          },
          "approvedWithModifications": {
            "type": "integer"
          },
          "rejected": {
            "type": "integer"
          },
          "approvalRate": {
            "type": "number",
            "format": "float"
          },
          "modificationRate": {
            "type": "number",
            "format": "float"
          },
          "rejectionRate": {
            "type": "number",
            "format": "float"
          }
        },
        "required": [
          "total",
          "approved",
          "approvedWithModifications",
          "rejected",
          "approvalRate",
          "modificationRate",
          "rejectionRate"
        ]
      },
      "SlaMetrics": {
        "type": "object",
        "properties": {
          "totalCompleted": {
            "type": "integer"
          },
          "metDeadline": {
            "type": "integer"
          },
          "breachedDeadline": {
            "type": "integer"
          },
          "complianceRate": {
            "type": "number",
            "format": "float"
          },
          "averageHoursToComplete": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "p50HoursToComplete": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "p95HoursToComplete": {
            "type": "number",
            "format": "float",
            "nullable": true
          }
        },
        "required": [
          "totalCompleted",
          "metDeadline",
          "breachedDeadline",
          "complianceRate"
        ]
      },
      "MonthlyOutcomeBucket": {
        "type": "object",
        "properties": {
          "month": {
            "type": "string",
            "description": "YYYY-MM."
          },
          "total": {
            "type": "integer"
          },
          "approved": {
            "type": "integer"
          },
          "approvedWithModifications": {
            "type": "integer"
          },
          "rejected": {
            "type": "integer"
          }
        },
        "required": [
          "month",
          "total",
          "approved",
          "approvedWithModifications",
          "rejected"
        ]
      },
      "ReviewerWorkload": {
        "type": "object",
        "properties": {
          "reviewerId": {
            "type": "string"
          },
          "reviewerName": {
            "type": "string"
          },
          "fcaRef": {
            "type": "string",
            "nullable": true
          },
          "jobsCompleted": {
            "type": "integer"
          },
          "jobsApproved": {
            "type": "integer"
          },
          "jobsRejected": {
            "type": "integer"
          },
          "jobsModified": {
            "type": "integer"
          },
          "averageHoursToComplete": {
            "type": "number",
            "format": "float",
            "nullable": true
          }
        },
        "required": [
          "reviewerId",
          "reviewerName",
          "jobsCompleted",
          "jobsApproved",
          "jobsRejected",
          "jobsModified"
        ]
      },
      "DocumentTypeBreakdown": {
        "type": "object",
        "properties": {
          "documentType": {
            "type": "string"
          },
          "total": {
            "type": "integer"
          },
          "approved": {
            "type": "integer"
          },
          "rejected": {
            "type": "integer"
          },
          "modified": {
            "type": "integer"
          }
        },
        "required": [
          "documentType",
          "total",
          "approved",
          "rejected",
          "modified"
        ]
      },
      "ControlEvidence": {
        "type": "object",
        "properties": {
          "totalReviewActions": {
            "type": "integer"
          },
          "totalChecklistItemsChecked": {
            "type": "integer"
          },
          "totalAnnotations": {
            "type": "integer"
          },
          "averageActionsPerJob": {
            "type": "number",
            "format": "float"
          },
          "averageReadCompletionRate": {
            "type": "number",
            "format": "float",
            "description": "0 to 1 \u2014 average share of a document read before a decision was recorded."
          }
        },
        "required": [
          "totalReviewActions",
          "totalChecklistItemsChecked",
          "totalAnnotations",
          "averageActionsPerJob",
          "averageReadCompletionRate"
        ]
      },
      "CryptoAnchor": {
        "type": "object",
        "description": "Immutable-ledger anchor for the reporting period, proving the metrics were computed over a specific tamper-evident record range.",
        "properties": {
          "firstSequenceNumber": {
            "type": "integer",
            "nullable": true
          },
          "lastSequenceNumber": {
            "type": "integer",
            "nullable": true
          },
          "lastChainHash": {
            "type": "string",
            "nullable": true
          },
          "recordCount": {
            "type": "integer"
          }
        },
        "required": [
          "recordCount"
        ]
      },
      "ConsumerDutyMetrics": {
        "type": "object",
        "properties": {
          "outcomes": {
            "$ref": "#/components/schemas/OutcomeBreakdown"
          },
          "sla": {
            "$ref": "#/components/schemas/SlaMetrics"
          },
          "reviewerWorkload": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReviewerWorkload"
            }
          },
          "documentTypeBreakdown": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DocumentTypeBreakdown"
            }
          },
          "controlEvidence": {
            "$ref": "#/components/schemas/ControlEvidence"
          },
          "monthlyBuckets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MonthlyOutcomeBucket"
            }
          },
          "cryptoAnchor": {
            "$ref": "#/components/schemas/CryptoAnchor"
          },
          "previousPeriod": {
            "type": "object",
            "nullable": true,
            "description": "Same window length immediately before the reporting period, for comparison.",
            "properties": {
              "outcomes": {
                "$ref": "#/components/schemas/OutcomeBreakdown"
              },
              "sla": {
                "$ref": "#/components/schemas/SlaMetrics"
              }
            },
            "required": [
              "outcomes",
              "sla"
            ]
          }
        },
        "required": [
          "outcomes",
          "sla",
          "reviewerWorkload",
          "documentTypeBreakdown",
          "controlEvidence",
          "monthlyBuckets",
          "cryptoAnchor"
        ]
      },
      "ConsumerDutyReportResponse": {
        "type": "object",
        "properties": {
          "periodFrom": {
            "type": "string",
            "format": "date-time"
          },
          "periodTo": {
            "type": "string",
            "format": "date-time",
            "description": "Inclusive end of the reporting period."
          },
          "metrics": {
            "$ref": "#/components/schemas/ConsumerDutyMetrics"
          }
        },
        "required": [
          "periodFrom",
          "periodTo",
          "metrics"
        ]
      },
      "Report": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "firmId": {
            "type": "string"
          },
          "type": {
            "$ref": "#/components/schemas/ReportType"
          },
          "periodFrom": {
            "type": "string",
            "format": "date-time"
          },
          "periodTo": {
            "type": "string",
            "format": "date-time"
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "generatedByUserId": {
            "type": "string",
            "nullable": true
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          },
          "generatedByUser": {
            "type": "object",
            "nullable": true,
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "email": {
                "type": "string"
              }
            }
          }
        },
        "required": [
          "id",
          "firmId",
          "type",
          "periodFrom",
          "periodTo",
          "generatedAt",
          "metadata"
        ]
      },
      "CreateReportLogParams": {
        "type": "object",
        "properties": {
          "type": {
            "$ref": "#/components/schemas/ReportType"
          },
          "periodFrom": {
            "type": "string",
            "format": "date-time"
          },
          "periodTo": {
            "type": "string",
            "format": "date-time"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          }
        },
        "required": [
          "type",
          "periodFrom",
          "periodTo"
        ]
      }
    },
    "securitySchemes": {
      "apiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Bedrock-Key",
        "description": "API key for firm authentication"
      }
    }
  }
}
