{
  "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://bedrockgovernance.com"
    }
  },
  "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": "Sandbox"
    }
  ],
  "tags": [
    {
      "name": "Public",
      "description": "Health check, public key, and certificate verification endpoints"
    },
    {
      "name": "Firm",
      "description": "Firm profile, settings, API keys, webhooks, and users"
    },
    {
      "name": "Governance",
      "description": "Impact assessments, incidents, vulnerability, bias, models, explainability, and reports"
    },
    {
      "name": "Ledger",
      "description": "Immutable ledger records, certificates, and chain integrity"
    },
    {
      "name": "Principal",
      "description": "Review job submission and tracking"
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "tags": [
          "Public"
        ],
        "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": [
          "Public"
        ],
        "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": "ECDSA P-256"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/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"
                      ]
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "settings": {
                      "$ref": "#/components/schemas/FirmSettings"
                    }
                  }
                }
              }
            }
          },
          "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 \u2014 nothing is written to the database and no ledger record is emitted.",
        "operationId": "updateFirmSettings",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FirmSettings"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated firm settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FirmSettings"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      },
      "get": {
        "tags": [
          "Firm"
        ],
        "summary": "Get firm settings",
        "description": "Returns the current firm settings including bias thresholds, Consumer Duty thresholds, and impact-assessment enforcement.",
        "operationId": "getFirmSettings",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Current firm settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FirmSettings"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/impact-assessments": {
      "get": {
        "tags": [
          "Governance"
        ],
        "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": [
          "Governance"
        ],
        "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/impact-assessments/{id}": {
      "get": {
        "tags": [
          "Governance"
        ],
        "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": [
          "Governance"
        ],
        "summary": "Update or transition an impact assessment",
        "description": "Edits the draft content and/or moves the assessment through its status graph: DRAFT \u2192 PENDING_SIGNOFF \u2192 APPROVED / REJECTED \u2192 SUPERSEDED. Approving requires an authenticated LEAD_REVIEWER or FIRM_ADMIN and produces an IMPACT_ASSESSMENT_APPROVED ledger event. APPROVED and SUPERSEDED assessments are immutable \u2014 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) \u2014 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/impact-assessments/{id}/supersede": {
      "post": {
        "tags": [
          "Governance"
        ],
        "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/models": {
      "get": {
        "tags": [
          "Governance"
        ],
        "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/models/drift": {
      "get": {
        "tags": [
          "Governance"
        ],
        "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/models/{provider}/{version}/timeline": {
      "get": {
        "tags": [
          "Governance"
        ],
        "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",
                  "fcaRef"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "fcaRef": {
                    "type": "string",
                    "description": "FCA individual reference number"
                  },
                  "qualifications": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "CFA",
                        "CFP",
                        "DIP_PFS",
                        "CISI",
                        "IMC"
                      ]
                    }
                  },
                  "role": {
                    "type": "string",
                    "enum": [
                      "FIRM_ADMIN",
                      "REVIEWER",
                      "LEAD_REVIEWER"
                    ],
                    "default": "REVIEWER"
                  },
                  "specialistVulnerability": {
                    "type": "boolean",
                    "description": "Set to true to mark this user as an FG21/1 specialist who is allowed to handle jobs carrying vulnerability flags. Defaults to false.",
                    "default": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "User invited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/User"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/firm/me/users/{id}": {
      "post": {
        "tags": [
          "Firm"
        ],
        "summary": "Update a user",
        "description": "Toggle the FG21/1 specialist flag on a user, or change their role. Specialist-flag updates accept either credential; role changes require a JWT-authenticated FIRM_ADMIN (API keys cannot promote users \u2014 passing a `role` field with an API-key credential returns 403 FORBIDDEN).",
        "operationId": "updateUser",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "User ID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "specialistVulnerability": {
                    "type": "boolean",
                    "description": "FG21/1 specialist flag. Reviewers with this flag (along with LEAD_REVIEWER and FIRM_ADMIN) are eligible to handle jobs with vulnerability flags."
                  },
                  "role": {
                    "type": "string",
                    "enum": [
                      "FIRM_ADMIN",
                      "REVIEWER",
                      "LEAD_REVIEWER"
                    ],
                    "description": "Only firm admins authenticated as users (not API keys) can change roles."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated user",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/User"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN \u2014 role changes require a JWT-authenticated firm admin"
          },
          "404": {
            "description": "User not found"
          }
        }
      },
      "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",
                  "required": [
                    "data",
                    "pagination"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Record"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "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"
                    },
                    "certificateId": {
                      "type": "string",
                      "description": "ID of the certificate"
                    }
                  }
                }
              }
            }
          },
          "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` \u2014 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/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/uploads": {
      "post": {
        "tags": [
          "Principal"
        ],
        "summary": "Get a presigned URL to upload an advice document",
        "description": "Step 1 of the two-step submission flow. Returns a short-lived presigned S3 URL the client can `PUT` the raw document bytes to, plus the `documentKey` to pass back to `POST /v1/principal/jobs`. The presigned URL is single-use and scoped to the firm \u2014 Bedrock never proxies the document bytes itself.",
        "operationId": "createUpload",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "filename"
                ],
                "properties": {
                  "filename": {
                    "type": "string",
                    "description": "Original filename, used as the object key suffix. The full key is namespaced under the firm so collisions across firms are impossible.",
                    "example": "suitability-12345.pdf"
                  },
                  "contentType": {
                    "type": "string",
                    "description": "MIME type used to sign the upload URL. Must match the `Content-Type` header on the subsequent `PUT`. Defaults to `application/pdf`.",
                    "default": "application/pdf",
                    "example": "application/pdf"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Presigned upload URL and document key",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "uploadUrl",
                    "documentKey"
                  ],
                  "properties": {
                    "uploadUrl": {
                      "type": "string",
                      "format": "uri",
                      "description": "Presigned S3 URL \u2014 `PUT` the document bytes here within the URL\u2019s expiry window."
                    },
                    "documentKey": {
                      "type": "string",
                      "description": "Opaque object key. Pass this back to `POST /v1/principal/jobs` as the `documentKey` field after the upload completes."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/principal/jobs": {
      "post": {
        "tags": [
          "Principal"
        ],
        "summary": "Submit an uploaded document for review",
        "description": "Step 2 of the two-step submission flow. The `documentKey` must be the value returned from a prior `POST /v1/principal/uploads` call \u2014 Bedrock reads the document bytes from the uploads bucket, hashes them, copies them into the immutable ledger bucket, and writes a `DOCUMENT_SUBMITTED` ledger event. There is no Bedrock-initiated download.",
        "operationId": "submitJob",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "documentKey",
                  "documentType",
                  "clientReference",
                  "documentReference",
                  "factFindSummary"
                ],
                "properties": {
                  "documentKey": {
                    "type": "string",
                    "description": "Opaque object key returned from `POST /v1/principal/uploads`. The document must already have been uploaded to the presigned URL when this call is made."
                  },
                  "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"
                    ]
                  },
                  "aiContext": {
                    "$ref": "#/components/schemas/AiContext"
                  },
                  "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/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"
          },
          "404": {
            "description": "DOCUMENT_NOT_FOUND \u2014 the `documentKey` does not resolve to an uploaded object in the firm's uploads bucket. Make sure the `PUT` to the presigned upload URL completed successfully before submitting the job."
          },
          "409": {
            "description": "IMPACT_ASSESSMENT_REQUIRED \u2014 the firm has the impact-assessment gate on (the default) and no approved assessment matches the aiContext.model.provider/version 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",
                  "required": [
                    "data",
                    "pagination"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Job"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "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/incidents": {
      "get": {
        "tags": [
          "Governance"
        ],
        "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": [
          "Governance"
        ],
        "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/incidents/stats": {
      "get": {
        "tags": [
          "Governance"
        ],
        "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/incidents/{id}": {
      "get": {
        "tags": [
          "Governance"
        ],
        "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": [
          "Governance"
        ],
        "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/incidents/{id}/remediations": {
      "post": {
        "tags": [
          "Governance"
        ],
        "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/incidents/{id}/remediations/{remediationId}": {
      "post": {
        "tags": [
          "Governance"
        ],
        "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/vulnerability": {
      "get": {
        "tags": [
          "Governance"
        ],
        "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/bias": {
      "get": {
        "tags": [
          "Governance"
        ],
        "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/reports/consumer-duty": {
      "get": {
        "tags": [
          "Governance"
        ],
        "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/reports": {
      "get": {
        "tags": [
          "Governance"
        ],
        "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": [
          "Governance"
        ],
        "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"
          }
        }
      }
    },
    "/v1/ledger/integrity": {
      "get": {
        "tags": [
          "Ledger"
        ],
        "summary": "Chain integrity status with verification history",
        "operationId": "integrityStatus",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Integrity status including last verification and history",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "firmId": {
                      "type": "string"
                    },
                    "totalRecords": {
                      "type": "integer"
                    },
                    "lastVerification": {
                      "type": "object",
                      "nullable": true,
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "verifiedAt": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "triggeredBy": {
                          "type": "string",
                          "enum": [
                            "manual",
                            "scheduled"
                          ]
                        },
                        "isValid": {
                          "type": "boolean"
                        },
                        "firstInvalidRecordId": {
                          "type": "string",
                          "nullable": true
                        },
                        "firstInvalidSequenceNumber": {
                          "type": "integer",
                          "nullable": true
                        },
                        "invalidReason": {
                          "type": "string",
                          "nullable": true,
                          "enum": [
                            "HASH_MISMATCH",
                            "SIGNATURE_INVALID",
                            "SEQUENCE_GAP",
                            "PREVIOUS_HASH_MISMATCH"
                          ]
                        }
                      }
                    },
                    "recentVerifications": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "verifiedAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "triggeredBy": {
                            "type": "string"
                          },
                          "isValid": {
                            "type": "boolean"
                          },
                          "invalidReason": {
                            "type": "string",
                            "nullable": true
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/ledger/integrity/verify": {
      "post": {
        "tags": [
          "Ledger"
        ],
        "summary": "Run a fresh chain verification",
        "description": "Recomputes every record hash from its payload fields and verifies chain links. The result is persisted and returned.",
        "operationId": "verifyNow",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Verification result with discrepancy detail (if any)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "verification": {
                      "type": "object"
                    },
                    "result": {
                      "type": "object",
                      "properties": {
                        "isValid": {
                          "type": "boolean"
                        },
                        "totalRecords": {
                          "type": "integer"
                        },
                        "firstInvalidRecordId": {
                          "type": "string",
                          "nullable": true
                        },
                        "firstInvalidSequenceNumber": {
                          "type": "integer",
                          "nullable": true
                        },
                        "invalidReason": {
                          "type": "string",
                          "nullable": true
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/ledger/integrity/records/{id}/discrepancy": {
      "get": {
        "tags": [
          "Ledger"
        ],
        "summary": "Compare a record against its immutable backup",
        "description": "Returns a field-by-field diff between the database row and the S3 immutable copy. If the record was deleted, `database` is null.",
        "operationId": "recordDiscrepancy",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Ledger record ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Discrepancy detail",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "recordId": {
                      "type": "string"
                    },
                    "sequenceNumber": {
                      "type": "integer"
                    },
                    "database": {
                      "type": "object",
                      "nullable": true
                    },
                    "immutable": {
                      "type": "object",
                      "nullable": true
                    },
                    "diffs": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "field": {
                            "type": "string"
                          },
                          "databaseValue": {
                            "type": "string"
                          },
                          "immutableValue": {
                            "type": "string",
                            "nullable": true
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Record not found in database or S3"
          }
        }
      }
    },
    "/v1/ledger/integrity/records/{id}/restore": {
      "post": {
        "tags": [
          "Ledger"
        ],
        "summary": "Restore a tampered record from its immutable backup",
        "description": "Overwrites the database row (or re-inserts a deleted record) from the S3 immutable copy. Requires FIRM_ADMIN or LEAD_REVIEWER role. A CHAIN_REPAIRED audit record is written to the ledger.",
        "operationId": "restoreRecord",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Ledger record ID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "confirm"
                ],
                "properties": {
                  "confirm": {
                    "type": "boolean",
                    "description": "Must be true to proceed"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Restore result",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "properties": {
                        "restored": {
                          "type": "boolean",
                          "enum": [
                            false
                          ]
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "properties": {
                        "restored": {
                          "type": "boolean",
                          "enum": [
                            true
                          ]
                        },
                        "recordId": {
                          "type": "string"
                        },
                        "diffs": {
                          "type": "array",
                          "items": {
                            "type": "object"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Missing confirm: true"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Insufficient role"
          },
          "404": {
            "description": "Record or immutable copy not found"
          }
        }
      }
    },
    "/v1/ledger/integrity/missing/{sequenceNumber}": {
      "get": {
        "tags": [
          "Ledger"
        ],
        "summary": "Find a deleted record by sequence number from S3",
        "description": "Scans the immutable S3 backup for a record with the given sequence number that no longer exists in the database. Used to recover deleted records detected by chain verification.",
        "operationId": "findMissingRecord",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "sequenceNumber",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The sequence number of the missing record"
          }
        ],
        "responses": {
          "200": {
            "description": "Discrepancy detail with database set to null and immutable copy from S3",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "recordId": {
                      "type": "string"
                    },
                    "sequenceNumber": {
                      "type": "integer"
                    },
                    "database": {
                      "type": "object",
                      "nullable": true
                    },
                    "immutable": {
                      "type": "object",
                      "nullable": true
                    },
                    "diffs": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid sequence number"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "No S3 record found for this sequence number"
          }
        }
      }
    },
    "/v1/explainability/search": {
      "get": {
        "tags": [
          "Governance"
        ],
        "summary": "Search explainability records by client reference",
        "operationId": "searchExplainability",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "clientReference",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Client reference to search for"
          }
        ],
        "responses": {
          "200": {
            "description": "List of explainability records matching the client reference",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ExplainabilityRecord"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/explainability/{jobId}": {
      "get": {
        "tags": [
          "Governance"
        ],
        "summary": "Get explainability record for a completed review job",
        "operationId": "getExplainabilityRecord",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Review job ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Explainability record with job, reviewer, certificate, and ledger details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExplainabilityRecord"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Job not found or not completed"
          }
        }
      }
    },
    "/v1/firm/me/document-types": {
      "get": {
        "tags": [
          "Firm"
        ],
        "summary": "List all document types for the firm",
        "description": "Returns all document types (platform defaults and custom) with their checklist items. Use the slug when submitting review jobs.",
        "operationId": "listDocumentTypes",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "List of document types",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/DocumentType"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "Firm"
        ],
        "summary": "Create a custom document type",
        "operationId": "createDocumentType",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "slug",
                  "name",
                  "checklistItems"
                ],
                "properties": {
                  "slug": {
                    "type": "string",
                    "description": "Unique identifier, uppercase with underscores (e.g. PENSION_TRANSFER)"
                  },
                  "name": {
                    "type": "string",
                    "description": "Display name"
                  },
                  "checklistItems": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "minItems": 1,
                    "description": "Review checklist items (at least one required)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Document type created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentType"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input (empty slug, name, or checklist)"
          },
          "401": {
            "description": "Unauthorized"
          },
          "409": {
            "description": "Slug already exists for this firm"
          }
        }
      }
    },
    "/v1/firm/me/document-types/{slug}": {
      "patch": {
        "tags": [
          "Firm"
        ],
        "summary": "Update a document type",
        "description": "Update the name and/or checklist items for a document type.",
        "operationId": "updateDocumentType",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Document type slug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Updated display name"
                  },
                  "checklistItems": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "minItems": 1,
                    "description": "Updated checklist items"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Document type updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentType"
                }
              }
            }
          },
          "400": {
            "description": "Empty body, empty name, or empty checklist"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Document type not found"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "AiContext": {
        "type": "object",
        "description": "AI lineage captured at advice generation time. Hashed into the ledger record alongside the document, making the input-to-output mapping immutable and independently verifiable.",
        "required": ["model"],
        "properties": {
          "model": {
            "type": "object",
            "required": ["provider", "version"],
            "description": "Which model produced the advice.",
            "properties": {
              "provider": { "type": "string", "example": "openai" },
              "version": { "type": "string", "example": "gpt-4o-2024-08-06" }
            }
          },
          "inputs": {
            "type": "object",
            "description": "Key inputs the model received.",
            "additionalProperties": true
          },
          "outputs": {
            "type": "object",
            "description": "What the model recommended.",
            "additionalProperties": true
          },
          "factors": {
            "type": "array",
            "description": "Which inputs most influenced the output.",
            "items": {
              "type": "object",
              "properties": {
                "input": { "type": "string" },
                "influence": { "type": "number" },
                "direction": { "type": "string", "enum": ["positive", "negative", "neutral"] }
              }
            }
          },
          "confidence": {
            "type": "number",
            "description": "Model confidence in the recommendation (0-1).",
            "minimum": 0,
            "maximum": 1
          },
          "guardrails": {
            "type": "array",
            "description": "Guardrails evaluated during generation.",
            "items": {
              "type": "object",
              "properties": {
                "rule": { "type": "string" },
                "triggered": { "type": "boolean" },
                "action": { "type": "string" }
              }
            }
          }
        }
      },
      "Pagination": {
        "type": "object",
        "description": "Cursor-less pagination envelope returned by paginated list endpoints (currently `/v1/ledger/records` and `/v1/principal/jobs`). Page numbers are 1-indexed.",
        "required": [
          "page",
          "pageSize",
          "totalCount",
          "totalPages",
          "hasNext",
          "hasPrevious"
        ],
        "properties": {
          "page": {
            "type": "integer",
            "description": "Current 1-indexed page number."
          },
          "pageSize": {
            "type": "integer",
            "description": "Number of items per page."
          },
          "totalCount": {
            "type": "integer",
            "description": "Total number of items across all pages."
          },
          "totalPages": {
            "type": "integer",
            "description": "Total number of pages."
          },
          "hasNext": {
            "type": "boolean"
          },
          "hasPrevious": {
            "type": "boolean"
          }
        }
      },
      "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",
            "description": "Human-assigned label, surfaced in audit logs and the settings UI."
          },
          "lastFourChars": {
            "type": "string",
            "description": "Last four characters of the raw key, kept for identification in the settings UI. The full key itself is only ever returned by the create endpoint."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastUsedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "revokedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "Webhook": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "firmId": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "secret": {
            "type": "string",
            "description": "HMAC secret used to sign deliveries to this endpoint. Returned in full from the create endpoint and on subsequent list calls \u2014 store it carefully."
          },
          "events": {
            "type": "array",
            "description": "Subscribed webhook event names. Only events on this list are delivered.",
            "items": {
              "type": "string",
              "enum": [
                "job.submitted",
                "review.started",
                "review.completed",
                "document.approved",
                "document.modified",
                "document.rejected",
                "chain.repaired",
                "sla.breached",
                "impact-assessment.approved",
                "incident.logged",
                "incident.resolved",
                "firm-settings.updated"
              ]
            }
          },
          "isActive": {
            "type": "boolean",
            "description": "Set to false by `DELETE /v1/firm/me/webhooks/{id}`. Inactive endpoints stay on the row but receive no further deliveries."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "User": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "fcaRef": {
            "type": "string",
            "nullable": true
          },
          "qualifications": {
            "type": "array",
            "description": "Professional qualifications used by the routing engine.",
            "items": {
              "type": "string",
              "enum": [
                "CFA",
                "CFP",
                "DIP_PFS",
                "CISI",
                "IMC"
              ]
            }
          },
          "role": {
            "type": "string",
            "enum": [
              "FIRM_ADMIN",
              "REVIEWER",
              "LEAD_REVIEWER"
            ]
          },
          "isAvailable": {
            "type": "boolean"
          },
          "specialistVulnerability": {
            "type": "boolean",
            "description": "FCA FG21/1 specialist flag. Reviewers with this flag set (along with LEAD_REVIEWER and FIRM_ADMIN) are eligible to handle jobs carrying any vulnerability flags."
          },
          "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 \u2014 shape depends on the linked record's eventType. Carries `eventLabel`, optional `statusPill`, and `sections` (heading + labelled fields)."
          }
        }
      },
      "Job": {
        "type": "object",
        "description": "A review job moving through the Principal workflow. Returned by `POST /v1/principal/jobs`, `GET /v1/principal/jobs`, and `GET /v1/principal/jobs/{id}`. Aliases the `ReviewJob` data-model entity.",
        "properties": {
          "id": {
            "type": "string"
          },
          "firmId": {
            "type": "string"
          },
          "documentReference": {
            "type": "string",
            "description": "Firm-assigned stable identifier for the document."
          },
          "documentType": {
            "type": "string",
            "description": "One of the `DocumentType` enum values."
          },
          "documentUrl": {
            "type": "string",
            "description": "Internal `s3://` URI of the canonical copy in the immutable ledger bucket. Not the upload URL \u2014 once the job is created the original `documentKey` is no longer relevant."
          },
          "documentHash": {
            "type": "string",
            "description": "sha256 of the document bytes as uploaded. The same hash is anchored on the `DOCUMENT_SUBMITTED` ledger record."
          },
          "clientReference": {
            "type": "string",
            "description": "Firm-assigned stable client identifier (not a name)."
          },
          "factFindSummary": {
            "type": "object",
            "description": "Structured summary of the client fact find as supplied at submission.",
            "additionalProperties": true
          },
          "status": {
            "type": "string",
            "enum": [
              "QUEUED",
              "ASSIGNED",
              "IN_REVIEW",
              "ESCALATED",
              "APPROVED",
              "MODIFIED",
              "REJECTED",
              "CANCELLED"
            ]
          },
          "priority": {
            "type": "string",
            "enum": [
              "STANDARD",
              "URGENT"
            ]
          },
          "outcome": {
            "type": "string",
            "nullable": true,
            "enum": [
              "APPROVED",
              "APPROVED_WITH_MODIFICATIONS",
              "REJECTED"
            ]
          },
          "outcomeReason": {
            "type": "string",
            "nullable": true,
            "description": "Reviewer-supplied reason recorded with the decision (most relevant on rejections)."
          },
          "modifications": {
            "type": "string",
            "nullable": true,
            "description": "Verbatim modifications recorded when the outcome is `APPROVED_WITH_MODIFICATIONS`."
          },
          "assignedReviewerId": {
            "type": "string",
            "nullable": true,
            "description": "FK \u2192 User. Set when the job moves to `ASSIGNED`."
          },
          "assignedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "completedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "submittedAt": {
            "type": "string",
            "format": "date-time"
          },
          "slaDeadline": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 deadline (`submittedAt + 48h`). Crossing this without a decision triggers an `SLA_BREACHED` ledger event."
          },
          "aiContext": {
            "nullable": true,
            "description": "AI lineage for the advice. Contains model identity, inputs, outputs, decision factors, confidence, and guardrails. Null when the submitting firm did not provide AI context.",
            "allOf": [{ "$ref": "#/components/schemas/AiContext" }]
          },
          "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/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) \u2014 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"
        ]
      },
      "ExplainabilityRecord": {
        "type": "object",
        "properties": {
          "job": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "documentReference": {
                "type": "string"
              },
              "documentType": {
                "type": "string"
              },
              "clientReference": {
                "type": "string"
              },
              "submittedAt": {
                "type": "string",
                "format": "date-time"
              },
              "slaDeadline": {
                "type": "string",
                "format": "date-time"
              },
              "completedAt": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "status": {
                "type": "string"
              },
              "outcome": {
                "type": "string",
                "nullable": true
              },
              "outcomeReason": {
                "type": "string",
                "nullable": true
              },
              "modifications": {
                "type": "string",
                "nullable": true
              },
              "reviewActions": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              }
            }
          },
          "reviewer": {
            "type": "object",
            "nullable": true,
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            }
          },
          "certificate": {
            "type": "object",
            "nullable": true,
            "properties": {
              "id": {
                "type": "string"
              },
              "issuedAt": {
                "type": "string",
                "format": "date-time"
              },
              "verificationUrl": {
                "type": "string",
                "format": "uri"
              }
            }
          },
          "ledgerRecord": {
            "type": "object",
            "nullable": true,
            "properties": {
              "id": {
                "type": "string"
              },
              "sequenceNumber": {
                "type": "integer"
              },
              "eventType": {
                "type": "string"
              },
              "recordHash": {
                "type": "string"
              },
              "chainHash": {
                "type": "string"
              },
              "signature": {
                "type": "string"
              },
              "timestamp": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "firm": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "frnNumber": {
                "type": "string"
              }
            }
          }
        }
      },
      "FirmSettings": {
        "type": "object",
        "description": "Firm-level configuration for bias thresholds, Consumer Duty thresholds, and governance controls.",
        "properties": {
          "biasWarningThreshold": {
            "type": "number",
            "description": "Bias score at which a warning is raised"
          },
          "biasAlertThreshold": {
            "type": "number",
            "description": "Bias score at which an alert is raised"
          },
          "biasAbsoluteRejectionThreshold": {
            "type": "number",
            "description": "Bias score at which automatic rejection occurs"
          },
          "cdRejectionRateWarningThreshold": {
            "type": "number",
            "description": "Consumer Duty rejection rate warning threshold"
          },
          "cdRejectionRateAlertThreshold": {
            "type": "number",
            "description": "Consumer Duty rejection rate alert threshold"
          },
          "cdSlaComplianceWarningThreshold": {
            "type": "number",
            "description": "Consumer Duty SLA compliance warning threshold"
          },
          "cdSlaComplianceAlertThreshold": {
            "type": "number",
            "description": "Consumer Duty SLA compliance alert threshold"
          },
          "cdReadCompletionWarningThreshold": {
            "type": "number",
            "description": "Consumer Duty read-completion warning threshold"
          },
          "enforceImpactAssessments": {
            "type": "boolean",
            "description": "When true, jobs submitted with aiContext.model are gated on a matching APPROVED impact assessment"
          }
        }
      },
      "DocumentType": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "firmId": {
            "type": "string"
          },
          "slug": {
            "type": "string",
            "description": "Unique identifier used in API payloads (e.g. SUITABILITY_REPORT)"
          },
          "name": {
            "type": "string",
            "description": "Display name (e.g. Suitability Report)"
          },
          "checklistItems": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Review checklist items for this document type"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      }
    },
    "securitySchemes": {
      "apiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Bedrock-Key",
        "description": "API key for firm authentication"
      },
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Cognito ID token for an authenticated firm user. Used by the Ledger and Principal web apps. Required for endpoints that change firm state under a named human (e.g. role changes, impact-assessment approvals)."
      }
    }
  }
}
