import { releaseVersion } from "../config/version.js"; const ref = (name: string) => ({ $ref: `#/components/schemas/${name}` }); const jsonContent = (schema: Record, example?: unknown) => ({ "application/json": { schema, ...(example === undefined ? {} : { example }) } }); const jsonResponse = (description: string, schema: Record, example?: unknown) => ({ description, content: jsonContent(schema, example) }); const serverError = jsonResponse("Internal or upstream service error.", ref("Error")); export const openApiDocument = { openapi: "3.1.1", info: { title: "RAG Service API", version: releaseVersion, description: "HTTP API for ingesting, retrieving, answering from, and evaluating scoped RAG knowledge." }, jsonSchemaDialect: "https://json-schema.org/draft/2020-12/schema", servers: [ { url: "https://rag.por-correo.com", description: "Production" }, { url: "http://localhost:3000", description: "Local development" } ], tags: [ { name: "Discovery", description: "API contract and browser playground." }, { name: "Status", description: "Service capabilities and available resources." }, { name: "Ingestion", description: "Knowledge ingestion and cleanup." }, { name: "OCR Review", description: "Authenticated OCR progress, review, correction, and decisions." }, { name: "Lifecycle", description: "Knowledge source versions, activation, rollback, and purge." }, { name: "Retrieval", description: "Context retrieval and model-backed answers." }, { name: "Evaluation", description: "Evaluation log capture and review." } ], paths: { "/help": { get: { tags: ["Discovery"], summary: "Discover the API", description: "Returns a concise endpoint catalog generated from this OpenAPI document.", responses: { "200": jsonResponse("API discovery catalog.", ref("HelpResponse")) } } }, "/openapi.json": { get: { tags: ["Discovery"], summary: "Get the OpenAPI contract", responses: { "200": jsonResponse("OpenAPI 3.1.1 document.", { type: "object" }) } } }, "/playground": { get: { tags: ["Discovery"], summary: "Open the browser playground", responses: { "200": { description: "Playground HTML page.", content: { "text/html": { schema: { type: "string" } } } } } } }, "/health": { get: { tags: ["Status"], summary: "Check service health and capabilities", responses: { "200": jsonResponse("Current service status.", ref("HealthResponse")), "503": jsonResponse("One or more required services are unavailable.", ref("HealthResponse")) } } }, "/sources": { get: { tags: ["Status"], summary: "List available knowledge scopes", responses: { "200": jsonResponse("Scopes currently stored in the vector database.", { type: "array", items: ref("AvailableScope") }), "503": serverError, "500": serverError } } }, "/sources/{sourceId}": { get: { tags: ["Lifecycle"], summary: "Get a catalog source", parameters: [{ name: "sourceId", in: "path", required: true, schema: { type: "string" } }], responses: { "200": jsonResponse("Catalog source.", ref("AvailableScope")), "404": jsonResponse("Source not found.", ref("Error")), "503": serverError } } }, "/sources/{sourceId}/versions": { get: { tags: ["Lifecycle"], summary: "List source versions", parameters: [{ name: "sourceId", in: "path", required: true, schema: { type: "string" } }], responses: { "200": jsonResponse("Source versions.", { type: "array", items: ref("SourceVersion") }), "503": serverError } } }, "/sources/{sourceId}/versions/{versionId}": { get: { tags: ["Lifecycle"], summary: "Get one source version", parameters: [ { name: "sourceId", in: "path", required: true, schema: { type: "string" } }, { name: "versionId", in: "path", required: true, schema: { type: "string", format: "uuid" } } ], responses: { "200": jsonResponse("Source version.", ref("SourceVersion")), "404": jsonResponse("Version not found.", ref("Error")), "503": serverError } }, delete: { tags: ["Lifecycle"], summary: "Purge a non-active source version", security: [{ bearerAuth: [] }], parameters: [ { name: "sourceId", in: "path", required: true, schema: { type: "string" } }, { name: "versionId", in: "path", required: true, schema: { type: "string", format: "uuid" } } ], responses: { "200": jsonResponse("Version purged.", ref("PurgeResponse")), "401": jsonResponse("Missing or invalid lifecycle admin token.", ref("Error")), "409": jsonResponse("Active versions cannot be purged.", ref("Error")), "503": serverError } } }, "/sources/{sourceId}/versions/{versionId}/activate": { post: { tags: ["Lifecycle"], summary: "Activate a ready or superseded version", security: [{ bearerAuth: [] }], parameters: [ { name: "sourceId", in: "path", required: true, schema: { type: "string" } }, { name: "versionId", in: "path", required: true, schema: { type: "string", format: "uuid" } } ], requestBody: { required: true, content: jsonContent(ref("ActivateVersionRequest")) }, responses: { "200": jsonResponse("Activated source version.", ref("SourceVersion")), "401": jsonResponse("Missing or invalid lifecycle admin token.", ref("Error")), "409": jsonResponse("Active version precondition failed.", ref("Error")), "503": serverError } } }, "/sources/{sourceId}/rollback": { post: { tags: ["Lifecycle"], summary: "Rollback a source to a previous version", security: [{ bearerAuth: [] }], parameters: [{ name: "sourceId", in: "path", required: true, schema: { type: "string" } }], requestBody: { required: true, content: jsonContent(ref("RollbackRequest")) }, responses: { "200": jsonResponse("Rollback activated source version.", ref("SourceVersion")), "400": jsonResponse("Required rollback fields are missing.", ref("Error")), "401": jsonResponse("Missing or invalid lifecycle admin token.", ref("Error")), "409": jsonResponse("Active version precondition failed.", ref("Error")), "503": serverError } } }, "/sources/{sourceId}/versions/{versionId}/retry": { post: { tags: ["Lifecycle"], summary: "Explicitly retry a failed or orphaned version by re-submitting a durable source", security: [{ bearerAuth: [] }], parameters: [ { name: "sourceId", in: "path", required: true, schema: { type: "string" } }, { name: "versionId", in: "path", required: true, schema: { type: "string", format: "uuid" } } ], requestBody: { required: false, content: jsonContent(ref("RetryVersionRequest")) }, responses: { "201": jsonResponse("Replacement version created by explicit retry.", ref("IngestResponse")), "202": jsonResponse("Retry instructions returned because no durable source payload was supplied.", ref("RetryVersionResponse")), "401": jsonResponse("Missing or invalid lifecycle admin token.", ref("Error")), "409": jsonResponse("Version state is not retryable.", ref("Error")), "503": serverError } } }, "/models/answer": { get: { tags: ["Status"], summary: "List answer models", description: "Returns the configured default and up to 80 selectable answer models.", responses: { "200": jsonResponse("Available answer models.", ref("AnswerModelsResponse")), "500": serverError } } }, "/ingest": { post: { tags: ["Ingestion"], summary: "Ingest a server-accessible file or folder", requestBody: { required: true, content: jsonContent(ref("IngestRequest"), { sourceType: "folder", sourceRef: "/data/project-docs", mode: "mechanical", tags: ["project-docs"] }) }, responses: { "201": jsonResponse("Lifecycle ingestion completed and committed.", ref("IngestResponse")), "202": jsonResponse("OCR ingestion accepted, or legacy ingestion accepted while lifecycle enforcement is disabled.", { oneOf: [ref("OcrAccepted"), ref("IngestResponse")] }), "400": jsonResponse("Invalid lifecycle activation request.", ref("Error")), "409": jsonResponse("Concurrent active version change or reusable version in progress.", ref("Error")), "422": jsonResponse("Empty or unsupported source.", ref("Error")), "503": serverError, "500": serverError } } }, "/ingest/upload": { post: { tags: ["Ingestion"], summary: "Upload and ingest a file or ZIP folder", requestBody: { required: true, content: { "multipart/form-data": { schema: ref("UploadIngestRequest") } } }, responses: { "201": jsonResponse("Lifecycle upload ingested successfully.", ref("UploadIngestResponse")), "202": jsonResponse("OCR upload accepted, or legacy upload accepted while lifecycle enforcement is disabled.", { oneOf: [ref("OcrUploadAccepted"), ref("UploadIngestResponse")] }), "400": jsonResponse("The file field is missing.", ref("Error"), { ok: false, error: "Missing file upload" }), "409": jsonResponse("Concurrent active version change or reusable version in progress.", ref("Error")), "422": jsonResponse("Empty or unsupported source.", ref("Error")), "503": serverError, "500": serverError } } }, "/ingestions/{versionId}": { get: { tags: ["OCR Review"], summary: "Get OCR ingestion progress", security: [{ bearerAuth: [] }], parameters: [{ name: "versionId", in: "path", required: true, schema: { type: "string", format: "uuid" } }], responses: { "200": jsonResponse("Current document and page progress.", ref("IngestionStatus")), "401": jsonResponse("Missing or invalid lifecycle admin token.", ref("Error")), "404": jsonResponse("OCR is disabled or the ingestion does not exist.", ref("Error")), "503": serverError, "500": serverError } } }, "/ingestions/{versionId}/review": { get: { tags: ["OCR Review"], summary: "Inspect an OCR candidate", security: [{ bearerAuth: [] }], parameters: [{ name: "versionId", in: "path", required: true, schema: { type: "string", format: "uuid" } }], responses: { "200": jsonResponse("Review candidate with page evidence.", ref("OcrReview")), "401": jsonResponse("Missing or invalid lifecycle admin token.", ref("Error")), "404": jsonResponse("OCR is disabled or the candidate does not exist.", ref("Error")), "409": jsonResponse("The version is not awaiting review.", ref("Error")), "503": serverError, "500": serverError } } }, "/ingestions/{versionId}/recover": { post: { tags: ["OCR Review"], summary: "Close an unrecoverable OCR review candidate", security: [{ bearerAuth: [] }], parameters: [{ name: "versionId", in: "path", required: true, schema: { type: "string", format: "uuid" } }], requestBody: { required: true, content: jsonContent(ref("OcrRecoveryRequest")) }, responses: { "200": jsonResponse("Candidate was closed without approval, indexing, or activation.", ref("OcrRecoveryResponse")), "400": jsonResponse("Recovery actor and reason are required.", ref("Error")), "401": jsonResponse("Missing or invalid lifecycle admin token.", ref("Error")), "404": jsonResponse("OCR candidate does not exist.", ref("Error")), "409": jsonResponse("Candidate state or evidence precondition does not permit recovery.", ref("Error")), "422": jsonResponse("OCR artifact format or integrity is invalid.", ref("Error")), "503": serverError, "500": serverError } } }, "/ingestions/{versionId}/documents/{documentId}/pages/{page}/image": { get: { tags: ["OCR Review"], summary: "Get a private OCR review page image", security: [{ bearerAuth: [] }], parameters: [ { name: "versionId", in: "path", required: true, schema: { type: "string", format: "uuid" } }, { name: "documentId", in: "path", required: true, schema: { type: "string" } }, { name: "page", in: "path", required: true, schema: { type: "integer", minimum: 1 } } ], responses: { "200": { description: "Integrity-validated private PNG review image.", content: { "image/png": { schema: { type: "string", format: "binary" } } } }, "401": jsonResponse("Missing or invalid lifecycle admin token.", ref("Error")), "404": jsonResponse("OCR is disabled or the review image does not exist.", ref("Error")), "503": serverError, "500": serverError } } }, "/ingestions/{versionId}/approve": { post: { tags: ["OCR Review"], summary: "Correct and approve an OCR candidate", security: [{ bearerAuth: [] }], parameters: [{ name: "versionId", in: "path", required: true, schema: { type: "string", format: "uuid" } }], requestBody: { required: true, content: jsonContent(ref("OcrApprovalRequest")) }, responses: { "200": jsonResponse("Candidate indexed and settled according to activation intent.", ref("OcrDecisionResponse")), "401": jsonResponse("Missing or invalid lifecycle admin token.", ref("Error")), "404": jsonResponse("OCR is disabled or the candidate does not exist.", ref("Error")), "409": jsonResponse("Candidate, correction, state, or active-version precondition conflict.", ref("Error")), "503": serverError, "500": serverError } } }, "/ingestions/{versionId}/reject": { post: { tags: ["OCR Review"], summary: "Reject an OCR candidate", security: [{ bearerAuth: [] }], parameters: [{ name: "versionId", in: "path", required: true, schema: { type: "string", format: "uuid" } }], requestBody: { required: true, content: jsonContent(ref("OcrRejectionRequest")) }, responses: { "200": jsonResponse("Candidate rejected without indexing or activation.", ref("OcrDecisionResponse")), "400": jsonResponse("Required rejection fields are missing.", ref("Error")), "401": jsonResponse("Missing or invalid lifecycle admin token.", ref("Error")), "404": jsonResponse("OCR is disabled or the candidate does not exist.", ref("Error")), "409": jsonResponse("Candidate hash or review state conflict.", ref("Error")), "503": serverError, "500": serverError } } }, "/cleanup": { post: { tags: ["Ingestion"], summary: "Delete chunks matching a source scope", description: "Requires sourceId or sourceRef to prevent accidental mass deletion. Tags alone are not accepted.", requestBody: { required: true, content: jsonContent(ref("CleanupRequest"), { scope: { sourceId: "customer-a:manual:v1" } }) }, responses: { "200": jsonResponse("Matching chunks deleted.", ref("CleanupResponse")), "400": jsonResponse("Scope is missing or unsafe for cleanup.", ref("Error")), "401": jsonResponse("Missing or invalid lifecycle admin token when lifecycle enforcement is active.", ref("Error")), "410": jsonResponse("Legacy cleanup is disabled while lifecycle enforcement is active.", ref("Error")), "503": serverError, "500": serverError } } }, "/retrieve": { post: { tags: ["Retrieval"], summary: "Retrieve scoped context", description: "Returns retrieved chunks. Set useModelInRetrieve to true to also generate modelSummary.", requestBody: { required: true, content: jsonContent(ref("RetrieveRequest"), { mode: "documental", intent: "specific", query: "How is the service deployed?", scope: { sourceId: "project-docs" }, useModelInRetrieve: false }) }, responses: { "200": jsonResponse("Retrieved context.", ref("RetrieveResponse")), "500": serverError } } }, "/answer": { post: { tags: ["Retrieval"], summary: "Answer using retrieved RAG context", requestBody: { required: true, content: jsonContent(ref("AnswerRequest")) }, responses: { "200": jsonResponse("Model answer with retrieval citations.", ref("AnswerResponse")), "500": serverError } } }, "/answer/direct": { post: { tags: ["Retrieval"], summary: "Answer without retrieving RAG context", description: "Primarily used to compare direct model output with RAG-backed output.", requestBody: { required: true, content: jsonContent(ref("DirectAnswerRequest")) }, responses: { "200": jsonResponse("Direct model answer.", ref("DirectAnswerResponse")), "500": serverError } } }, "/chat": { post: { tags: ["Retrieval"], summary: "Chat with optional bootstrap and additional retrieval", requestBody: { required: true, content: jsonContent(ref("ChatRequest")) }, responses: { "200": jsonResponse("Chat response and retrieval usage metadata.", ref("ChatResponse")), "500": serverError } } }, "/logs/recent": { get: { tags: ["Evaluation"], summary: "List recent evaluation logs", parameters: [{ name: "limit", in: "query", required: false, description: "Maximum number of logs. Defaults to 20.", schema: { type: "integer", default: 20 } }], responses: { "200": jsonResponse("Recent evaluation logs.", { type: "array", items: ref("EvaluationLog") }), "500": serverError } } }, "/logs/manual": { post: { tags: ["Evaluation"], summary: "Create a manual evaluation log", requestBody: { required: true, content: jsonContent(ref("ManualLogRequest")) }, responses: { "201": jsonResponse("Evaluation log created.", ref("EvaluationLog")), "500": serverError } } }, "/logs/{id}": { patch: { tags: ["Evaluation"], summary: "Update evaluation log review status", parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }], requestBody: { required: true, content: jsonContent(ref("UpdateLogRequest")) }, responses: { "200": jsonResponse("Updated evaluation log.", ref("EvaluationLog")), "404": jsonResponse("Evaluation log not found.", ref("Error"), { ok: false, error: "Log not found" }), "500": serverError } } } }, components: { securitySchemes: { bearerAuth: { type: "http", scheme: "bearer" } }, schemas: { Error: { type: "object", required: ["ok", "error"], properties: { ok: { type: "boolean", const: false }, error: { type: "string" }, code: { type: "string" }, action: { type: "string" } } }, Scope: { type: "object", properties: { sourceId: { type: "string" }, sourceRef: { type: "string" }, tags: { type: "array", items: { type: "string" } } } }, SourceVersionState: { type: "string", enum: ["pending", "indexing", "review_required", "ready", "active", "superseded", "failed", "rejected", "purging", "purged"] }, AvailableScope: { type: "object", required: ["sourceId", "sourceRef", "chunkModes", "tags"], properties: { sourceId: { type: "string" }, sourceRef: { type: "string" }, chunkModes: { type: "array", items: { $ref: "#/components/schemas/ChunkMode" } }, tags: { type: "array", items: { type: "string" } }, activeVersionId: { type: ["string", "null"], format: "uuid" }, activeVersionNumber: { type: ["integer", "null"] }, state: { ...ref("SourceVersionState"), nullable: true }, needsReingest: { type: "boolean" }, updatedAt: { type: "string", format: "date-time" } } }, ChunkMode: { type: "string", enum: ["documental", "codigo", "auto"] }, RetrieveIntent: { type: "string", enum: ["bootstrap", "specific"] }, IngestRequest: { type: "object", required: ["sourceType", "sourceRef"], properties: { sourceId: { type: "string" }, sourceType: { type: "string", enum: ["file", "folder"] }, sourceRef: { type: "string" }, readPath: { type: "string", description: "Internal filesystem path used when the logical sourceRef differs from the read location." }, mode: { type: "string", enum: ["mechanical", "interactive"], default: "mechanical" }, tags: { type: "array", items: { type: "string" } }, activate: { type: "boolean", default: true }, expectedActiveVersionId: { type: ["string", "null"], format: "uuid", description: "Required when activate is true. Use null for first activation." } } }, IngestResponse: { type: "object", required: ["accepted", "sourceId", "filesDiscovered", "documentsProcessed", "chunksStored", "collectionName"], properties: { accepted: { type: "boolean", const: true }, source: ref("IngestRequest"), sourceId: { type: "string" }, versionId: { type: "string", format: "uuid" }, versionNumber: { type: "integer" }, state: ref("SourceVersionState"), previousVersionId: { type: ["string", "null"], format: "uuid" }, filesDiscovered: { type: "integer" }, documentsProcessed: { type: "integer" }, chunksStored: { type: "integer" }, activated: { type: "boolean" }, noOp: { type: "boolean" }, collectionName: { type: "string" } } }, UploadIngestRequest: { type: "object", required: ["file"], properties: { file: { type: "string", format: "binary" }, sourceId: { type: "string" }, sourceRef: { type: "string", minLength: 1, description: "Optional logical source reference; trimmed and defaults to the uploaded filename." }, mode: { type: "string", enum: ["mechanical", "interactive"], default: "mechanical" }, tags: { type: "string", description: "Comma-separated tags." }, isZipFolder: { type: "string", enum: ["true", "false"], default: "false" }, activate: { type: "string", enum: ["true", "false"], default: "true" }, expectedActiveVersionId: { type: "string", description: "UUID or literal null when activate is true." } } }, UploadIngestResponse: { allOf: [ ref("IngestResponse"), { type: "object", required: ["uploadedResource"], properties: { uploadedResource: { type: "string" } } } ] }, OcrPhase: { type: "string", enum: ["native_extracting", "ocr_queued", "ocr_running", "review_required", "indexing", "ready", "active", "failed", "rejected"] }, OcrAccepted: { type: "object", required: ["accepted", "sourceId", "versionId", "versionNumber", "state", "phase", "statusUrl", "reviewUrl", "activated"], properties: { accepted: { type: "boolean", const: true }, sourceId: { type: "string" }, versionId: { type: "string", format: "uuid" }, versionNumber: { type: "integer" }, state: { type: "string", const: "indexing" }, phase: { type: "string", const: "ocr_queued" }, statusUrl: { type: "string" }, reviewUrl: { type: "null" }, activated: { type: "boolean", const: false } } }, OcrUploadAccepted: { allOf: [ref("OcrAccepted"), { type: "object", required: ["uploadedResource"], properties: { uploadedResource: { type: "string" } } }] }, IngestionStatus: { type: "object", required: ["sourceId", "versionId", "state", "phase", "activated", "documents", "error", "statusUrl", "reviewUrl"], properties: { sourceId: { type: "string" }, versionId: { type: "string", format: "uuid" }, state: ref("SourceVersionState"), phase: ref("OcrPhase"), activated: { type: "boolean" }, documents: { type: "array", items: ref("OcrProgressDocument") }, error: { oneOf: [ref("OcrStatusError"), { type: "null" }] }, statusUrl: { type: "string" }, reviewUrl: { type: ["string", "null"] } } }, OcrProgressDocument: { type: "object", required: ["documentId", "state", "completedPages", "totalPages", "pages"], properties: { documentId: { type: "string" }, state: { type: "string", enum: ["native_complete", "ocr_queued", "ocr_running", "ocr_complete", "failed"] }, completedPages: { type: "integer" }, totalPages: { type: "integer" }, pages: { type: "array", items: ref("OcrProgressPage") } } }, OcrProgressPage: { type: "object", required: ["page", "method", "state"], properties: { page: { type: "integer" }, method: { type: "string", enum: ["native", "ocr", "blank"] }, state: { type: "string", enum: ["pending", "native_complete", "ocr_queued", "ocr_running", "ocr_complete", "blank", "failed"] }, errorCode: { type: "string" } } }, OcrStatusError: { type: "object", required: ["code", "message", "retryable"], properties: { code: { type: "string" }, message: { type: "string" }, retryable: { type: "boolean" } } }, OcrLine: { type: "object", required: ["lineId", "text", "confidence", "bbox", "lineSha256"], properties: { lineId: { type: "string" }, text: { type: "string" }, confidence: { type: "number" }, bbox: { type: "array", items: { type: "number" }, minItems: 4, maxItems: 4 }, lineSha256: { type: "string", pattern: "^[a-f0-9]{64}$" } } }, OcrReview: { type: "object", required: ["versionId", "sourceId", "state", "candidateSha256", "baseActiveVersionId", "currentActiveVersionId", "activateRequested", "processingFingerprint", "metadataHash", "documents"], properties: { versionId: { type: "string", format: "uuid" }, sourceId: { type: "string" }, state: { type: "string", const: "review_required" }, candidateSha256: { type: "string", pattern: "^[a-f0-9]{64}$" }, baseActiveVersionId: { type: ["string", "null"], format: "uuid" }, currentActiveVersionId: { type: ["string", "null"], format: "uuid" }, activateRequested: { type: "boolean" }, processingFingerprint: { type: "string" }, metadataHash: { type: "string" }, documents: { type: "array", items: ref("OcrReviewDocument") } } }, OcrReviewDocument: { type: "object", required: ["documentId", "pages"], properties: { documentId: { type: "string" }, pages: { type: "array", items: ref("OcrReviewPage") } } }, OcrReviewPage: { type: "object", required: ["page", "imageUrl", "nativeText", "ocr", "candidateText", "differences", "risks"], properties: { page: { type: "integer" }, imageUrl: { type: ["string", "null"] }, nativeText: { type: "string" }, ocr: { type: "object", required: ["text", "lines"], properties: { text: { type: "string" }, lines: { type: "array", items: ref("OcrLine") } } }, candidateText: { type: "string" }, differences: { type: "array", items: { type: "string" } }, risks: { type: "array", items: { type: "string" } } } }, OcrCorrection: { type: "object", required: ["documentId", "page", "lineId", "expectedLineSha256", "replacementText"], properties: { documentId: { type: "string" }, page: { type: "integer" }, lineId: { type: "string" }, expectedLineSha256: { type: "string", pattern: "^[a-f0-9]{64}$" }, replacementText: { type: "string" } } }, OcrApprovalRequest: { type: "object", required: ["candidateSha256", "expectedActiveVersionId", "reviewedBy", "corrections"], properties: { candidateSha256: { type: "string", pattern: "^[a-f0-9]{64}$" }, expectedActiveVersionId: { type: ["string", "null"], format: "uuid" }, reviewedBy: { type: "string" }, corrections: { type: "array", items: ref("OcrCorrection") } } }, OcrRejectionRequest: { type: "object", required: ["candidateSha256", "reviewedBy", "reason"], properties: { candidateSha256: { type: "string", pattern: "^[a-f0-9]{64}$" }, reviewedBy: { type: "string" }, reason: { type: "string" } } }, OcrRecoveryRequest: { type: "object", required: ["recoveredBy", "reason"], properties: { recoveredBy: { type: "string" }, reason: { type: "string" } } }, OcrRecoveryResponse: { type: "object", required: ["versionId", "state", "outcome"], properties: { versionId: { type: "string", format: "uuid" }, state: { type: "string", const: "failed" }, outcome: { type: "string", const: "closed_failed" } } }, OcrDecisionResponse: { type: "object", required: ["versionId", "state", "activated"], properties: { versionId: { type: "string", format: "uuid" }, state: { type: "string", enum: ["indexing", "rejected"] }, activated: { type: "boolean" } } }, CleanupRequest: { type: "object", required: ["scope"], properties: { scope: ref("Scope") } }, CleanupResponse: { type: "object", required: ["ok", "deleted"], properties: { ok: { type: "boolean" }, deleted: { type: "integer" } } }, BootstrapMeta: { type: "object", properties: { query: { type: "string" }, mode: ref("ChunkMode"), scope: ref("Scope"), model: { type: "string" }, usedModelSummary: { type: "boolean" } } }, RetrieveRequest: { type: "object", required: ["query"], properties: { mode: { ...ref("ChunkMode"), default: "auto" }, intent: { ...ref("RetrieveIntent"), default: "specific" }, query: { type: "string" }, scope: ref("Scope"), model: { type: "string", description: "Model override used only when useModelInRetrieve is true." }, useModelInRetrieve: { type: "boolean", default: false }, bootstrapMeta: ref("BootstrapMeta") } }, RetrievedItem: { type: "object", required: ["chunkId", "documentId", "sourceId", "title", "content", "score"], properties: { chunkId: { type: "string" }, documentId: { type: "string" }, sourceId: { type: "string" }, sourceVersionId: { type: "string", format: "uuid" }, sourceVersionNumber: { type: "integer" }, title: { type: "string" }, sectionTitle: { type: "string" }, content: { type: "string" }, score: { type: "number" }, chunkMode: ref("ChunkMode"), startLine: { type: "integer" }, endLine: { type: "integer" } } }, RetrieveResponse: { type: "object", required: ["mode", "intent", "summary", "topics", "criticalPoints", "items", "followUpRefs"], properties: { mode: ref("ChunkMode"), intent: ref("RetrieveIntent"), model: { type: "string" }, summary: { type: "string" }, modelSummary: { type: "string" }, topics: { type: "array", items: { type: "string" } }, criticalPoints: { type: "array", items: { type: "string" } }, items: { type: "array", items: ref("RetrievedItem") }, followUpRefs: { type: "array", items: { type: "string" } }, scope: ref("Scope") } }, AnswerRequest: { type: "object", required: ["query"], properties: { mode: { ...ref("ChunkMode"), default: "auto" }, intent: { ...ref("RetrieveIntent"), default: "specific" }, query: { type: "string" }, scope: ref("Scope"), model: { type: "string" }, preloadedContext: { type: "string" }, bootstrapMeta: ref("BootstrapMeta") } }, Citation: { type: "object", required: ["chunkId", "documentId", "title"], properties: { chunkId: { type: "string" }, documentId: { type: "string" }, sourceVersionId: { type: "string", format: "uuid" }, sourceVersionNumber: { type: "integer" }, title: { type: "string" }, sectionTitle: { type: "string" }, startLine: { type: "integer" }, endLine: { type: "integer" } } }, AnswerResponse: { type: "object", required: ["mode", "intent", "model", "answer", "summary", "topics", "criticalPoints", "citations"], properties: { mode: ref("ChunkMode"), intent: ref("RetrieveIntent"), model: { type: "string" }, answer: { type: "string" }, summary: { type: "string" }, topics: { type: "array", items: { type: "string" } }, criticalPoints: { type: "array", items: { type: "string" } }, citations: { type: "array", items: ref("Citation") }, scope: ref("Scope") } }, DirectAnswerRequest: { type: "object", required: ["query"], properties: { query: { type: "string" }, model: { type: "string" }, preloadedContext: { type: "string" } } }, DirectAnswerResponse: { type: "object", required: ["model", "answer"], properties: { model: { type: "string" }, answer: { type: "string" } } }, ChatMessage: { type: "object", required: ["role", "content"], properties: { role: { type: "string", enum: ["user", "assistant"] }, content: { type: "string" } } }, ChatRequest: { type: "object", required: ["message"], properties: { message: { type: "string" }, history: { type: "array", items: ref("ChatMessage") }, mode: { ...ref("ChunkMode"), default: "documental" }, model: { type: "string" }, preloadedContext: { type: "string" }, allowAdditionalRetrieve: { type: "boolean", default: false }, scope: ref("Scope"), bootstrapMeta: ref("BootstrapMeta") } }, ChatResponse: { type: "object", required: ["model", "answer", "usedBootstrapContext", "usedAdditionalRetrieve"], properties: { model: { type: "string" }, answer: { type: "string" }, usedBootstrapContext: { type: "boolean" }, usedAdditionalRetrieve: { type: "boolean" }, retrieved: ref("RetrieveResponse") } }, EvaluationLog: { type: "object", required: ["id", "trigger", "operation", "reason", "query", "createdAt", "retrievedItemsCount", "chunkIds", "documentIds", "reviewStatus", "severity"], properties: { id: { type: "string" }, trigger: { type: "string", enum: ["automatic", "manual"] }, operation: { type: "string", enum: ["retrieve", "answer", "chat"] }, reason: { type: "string" }, query: { type: "string" }, mode: ref("ChunkMode"), intent: ref("RetrieveIntent"), scope: ref("Scope"), model: { type: "string" }, note: { type: "string" }, createdAt: { type: "string", format: "date-time" }, usedBootstrapContext: { type: "boolean" }, bootstrapQuery: { type: "string" }, bootstrapMode: ref("ChunkMode"), bootstrapScope: ref("Scope"), bootstrapModel: { type: "string" }, bootstrapUsedModelSummary: { type: "boolean" }, usedAdditionalRetrieve: { type: "boolean" }, responseSummary: { type: "string" }, retrievedItemsCount: { type: "integer" }, chunkIds: { type: "array", items: { type: "string" } }, documentIds: { type: "array", items: { type: "string" } }, reviewStatus: { type: "string", enum: ["pending", "in_progress", "resolved", "ignored"] }, severity: { type: "string", enum: ["low", "medium", "high"] }, reviewedAt: { type: "string", format: "date-time" }, reviewedBy: { type: "string" }, resolutionNote: { type: "string" }, fixReference: { type: "string" }, supersedesLogId: { type: "string" } } }, ManualLogRequest: { type: "object", required: ["query"], properties: { operation: { type: "string", enum: ["retrieve", "answer", "chat"], default: "answer" }, reason: { type: "string", default: "manual_review_requested" }, query: { type: "string" }, mode: ref("ChunkMode"), intent: ref("RetrieveIntent"), scope: ref("Scope"), model: { type: "string" }, note: { type: "string" }, usedBootstrapContext: { type: "boolean" }, bootstrapMeta: ref("BootstrapMeta"), usedAdditionalRetrieve: { type: "boolean" }, responseSummary: { type: "string" }, retrievedItems: { type: "array", items: ref("RetrievedItem") } } }, UpdateLogRequest: { type: "object", properties: { reviewStatus: { type: "string", enum: ["pending", "in_progress", "resolved", "ignored"] }, severity: { type: "string", enum: ["low", "medium", "high"] }, reviewedBy: { type: "string" }, resolutionNote: { type: "string" }, fixReference: { type: "string" }, supersedesLogId: { type: "string" } } }, HealthResponse: { type: "object", required: ["ok", "service", "version", "revision", "environment", "embeddings", "answer", "vectorStore", "postgres", "knowledgeLifecycle", "parsers", "chunking"], properties: { ok: { type: "boolean" }, service: { type: "string", const: "rag" }, version: { type: "string" }, revision: { type: "string" }, environment: { type: "string" }, embeddings: ref("ProviderModel"), answer: ref("ProviderModel"), vectorStore: { type: "object", additionalProperties: true }, postgres: { type: "object", additionalProperties: true }, knowledgeLifecycle: { type: "object", additionalProperties: true }, parsers: { type: "array", items: { type: "string" } }, chunking: { type: "object", additionalProperties: true } } }, SourceVersion: { type: "object", required: ["versionId", "sourceId", "versionNumber", "state", "tags", "embeddingProvider", "embeddingModel", "embeddingDimensions", "expectedDocumentCount", "expectedPointCount", "verifiedPointCount", "qdrantCollection"], properties: { versionId: { type: "string", format: "uuid" }, sourceId: { type: "string" }, versionNumber: { type: "integer" }, previousVersionId: { type: ["string", "null"], format: "uuid" }, state: ref("SourceVersionState"), tags: { type: "array", items: { type: "string" } }, sourceContentHash: { type: ["string", "null"] }, processingFingerprint: { type: "string" }, metadataHash: { type: "string" }, embeddingProvider: { type: "string" }, embeddingModel: { type: "string" }, embeddingDimensions: { type: "integer" }, expectedDocumentCount: { type: "integer" }, expectedPointCount: { type: "integer" }, verifiedPointCount: { type: "integer" }, qdrantCollection: { type: "string" } } }, ActivateVersionRequest: { type: "object", required: ["expectedActiveVersionId"], properties: { expectedActiveVersionId: { type: ["string", "null"], format: "uuid" } } }, RollbackRequest: { type: "object", required: ["targetVersionId", "expectedActiveVersionId"], properties: { targetVersionId: { type: "string", format: "uuid" }, expectedActiveVersionId: { type: "string", format: "uuid" } } }, RetryVersionRequest: { type: "object", properties: { sourceType: { type: "string", enum: ["file", "folder"] }, sourceRef: { type: "string" }, readPath: { type: "string" }, mode: { type: "string", enum: ["mechanical", "interactive"], default: "mechanical" }, tags: { type: "array", items: { type: "string" } }, activate: { type: "boolean", default: true }, expectedActiveVersionId: { type: ["string", "null"], format: "uuid" } } }, RetryVersionResponse: { type: "object", required: ["ok", "retryRequired", "sourceId", "versionId", "message"], properties: { ok: { type: "boolean", const: true }, retryRequired: { type: "boolean", const: true }, sourceId: { type: "string" }, versionId: { type: "string", format: "uuid" }, message: { type: "string" } } }, PurgeResponse: { type: "object", required: ["ok", "purged", "versionId"], properties: { ok: { type: "boolean", const: true }, purged: { type: "boolean", const: true }, versionId: { type: "string", format: "uuid" } } }, ProviderModel: { type: "object", required: ["provider", "model"], properties: { provider: { type: "string" }, model: { type: "string" } } }, AnswerModelsResponse: { type: "object", required: ["defaultModel", "models"], properties: { defaultModel: { type: "string" }, models: { type: "array", items: { type: "string" }, maxItems: 80 } } }, HelpEndpoint: { type: "object", required: ["method", "path", "summary", "tags"], properties: { method: { type: "string" }, path: { type: "string" }, summary: { type: "string" }, tags: { type: "array", items: { type: "string" } } } }, HelpResponse: { type: "object", required: ["service", "version", "description", "documentation", "authentication", "endpoints"], properties: { service: { type: "string" }, version: { type: "string" }, description: { type: "string" }, documentation: { type: "object", required: ["openapi", "openapiPurpose", "playground"], properties: { openapi: { type: "string" }, openapiPurpose: { type: "string" }, playground: { type: "string" } } }, authentication: { type: "string" }, endpoints: { type: "array", items: ref("HelpEndpoint") } } } } } } as const; type OpenApiOperation = { summary?: string; tags?: readonly string[]; }; export function buildApiHelp() { const endpoints = Object.entries(openApiDocument.paths).flatMap(([routePath, pathItem]) => Object.entries(pathItem).map(([method, operation]) => { const details = operation as OpenApiOperation; return { method: method.toUpperCase(), path: routePath, summary: details.summary ?? "", tags: details.tags ?? [] }; }) ); return { service: openApiDocument.info.title, version: openApiDocument.info.version, description: openApiDocument.info.description, documentation: { openapi: "/openapi.json", openapiPurpose: "Consult this contract for complete parameters, request bodies, responses, errors, and examples for every endpoint.", playground: "/playground" }, authentication: "Bearer authentication is required for lifecycle administration and all OCR candidate routes; other routes remain public.", endpoints }; }