Current Document Processing State
This page documents the current document ingestion paths while the unified document processing pipeline is being rolled out.
The target architecture keeps the original document metadata in documents,
the extracted text in document_texts, and the automatic processing state plus
parser outputs in document_processing_runs.
Entry points
Manual upload
Owner service: docs-service.
Current flow:
POST /docs/uploadreceives a multipart file.- The route requires
entity_typeandentity_id. - The route creates or reuses a
documentsrow. - The route uploads the binary to the configured storage provider.
- The route creates a
document_versionsrow. - The route updates the current storage metadata on
documents. - The route calls
extractAndStoreDocumentText. - Extracted text is persisted into
document_texts.
Relevant files:
backend/docs-service/routes/docs.jsbackend/docs-service/modules/text/extractAndStoreDocumentText.jsbackend/docs-service/modules/text/documentTextRepository.js
Current canonical writes:
documentsdocument_versionsdocument_texts
Email ingestion
Owner service: mail-ingestion-service, with file persistence delegated to
docs-service.
Current flow:
- Mail rules resolve attachments or downloadable links from a message.
- Actions such as
INDEX_DOCUMENT, utility bill parsing, and condominium installment parsing upload the file throughdocs-service /docs/upload. - Because upload goes through
docs-service, the file is saved indocuments,document_versions, anddocument_texts. - Mail-specific metadata is saved in
mail_message_documents. - The upload endpoint triggers a
document_processing_runspipeline run. - Parsing/classification results are written to
document_processing_runs, not materialized directly bymail-ingestion-service.
Relevant files:
backend/mail-ingestion-service/modules/rules/actions/indexDocument.action.jsbackend/mail-ingestion-service/modules/attachments/utilityBillAttachmentProcessor.jsbackend/mail-ingestion-service/modules/rules/actions/parseCondominiumInstallments.action.js
Current canonical writes:
documents, throughdocs-servicedocument_versions, throughdocs-servicedocument_texts, throughdocs-servicedocument_processing_runs, throughdocument-indexer-servicemail_message_documents
Mail-specific state kept outside the pipeline:
mail_messages.classification_jsonmail_message_documents.classification_json, only for source/link metadata and parser hints such asservice_contract_idortarget_personmail_message_documents.detected_document_type, as a compatibility hintmail_message_documents.processing_status, as mail-rule execution status
Cloud sync
Owner service: storage-connector-service, with indexing delegated to
document-indexer-service.
Current flow:
storage-connector-servicedetects provider changes.- The connector records the provider file metadata and source reference.
- The connector calls
document-indexer-service /index/storage-file. - The indexer queues the file as a low-priority storage job.
indexStorageFilecreates or updates adocumentsrow bysource_connection_idandsource_file_id.- The indexer creates a document processing run and publishes the pipeline event.
- The text extraction consumer calls
docs-service /docs/:id/ensure-text. docs-serviceretrieves cloud bytes on demand throughstorage-connector-service /connections/:id/files/content.docs-servicepersists extracted text indocument_texts.- Later pipeline steps classify, parse, index, and update document status.
Relevant files:
backend/storage-connector-service/modules/sync/syncEngine.jsbackend/storage-connector-service/modules/downstream/indexingClient.jsbackend/document-indexer-service/routes/indexingRoutes.jsbackend/document-indexer-service/modules/indexing/indexStorageFile.js
Current canonical writes:
storage_synced_filesdocumentsdocument_processing_runsdocument_texts, throughdocs-service /docs/:id/ensure-text- vector payloads in Qdrant
Current constraint:
- Cloud-origin files are not copied into rAInty storage during sync. The source file must still be reachable from the provider when the text extraction consumer runs.
Known duplicated automatic state:
storage_synced_files.statusstorage_synced_files.error_messagestorage_synced_files.metadata_jsondocuments.indexing_statusdocuments.indexing_error
Existing storage model
documents already supports source tracking:
source_typesource_connection_idsource_file_id
This means cloud-origin documents should not need a parallel business document
table. The same documents row can represent both rAInty-stored files and
external cloud files, with source fields identifying the origin.
document_texts already supports extracted text layers:
PDF_TEXTOCR_GOOGLELLM_RAG_TEXTMANUAL
This table should remain the canonical place for OCR/text extraction output.
Pipeline state target
document_processing_runs becomes the canonical table for automatic processing
state and parser outputs:
- pipeline status
- current step
- detected document type
- classification confidence
- classification JSON
- structured extraction JSON
- failure reason
- attempt count
Source-specific tables may keep source tracking data, but should not become the canonical place for document classification or parser results.
Source tracking versus pipeline result
Source tables must remain focused on origin tracking.
storage_synced_files keeps cloud sync metadata:
connection_idfolder_idprovider_file_id- provider filename/path/checksum/modified timestamp
- sync status
document_id
It must not store pipeline state such as processing run ids, parser output, or classification JSON.
mail_message_documents keeps mail attachment linkage metadata:
mail_message_iddocument_idprovider_attachment_idoriginal_file_name- source URL or similar attachment metadata when needed for deduplication
It must not store parsed document JSON or automatic pipeline output.
Legacy columns such as classification_json, detected_document_type, and
processing_status may still exist for backward compatibility and idempotency,
but the canonical automatic result is document_processing_runs.
Pipeline statuses:
| Status | Meaning |
|---|---|
REQUESTED | A processing run was created but no step has started yet. |
TEXT_EXTRACTION_PENDING | Text extraction or OCR has to run. |
TEXT_READY | Text is available in document_texts. |
CLASSIFICATION_PENDING | Document classification has to run. |
CLASSIFIED | Document type and classification metadata are available. |
PARSING_PENDING | Structured extraction has to run. |
PARSED | Structured extraction JSON is available. |
INDEXING_PENDING | Vector indexing has to run. |
INDEXED | Vector indexing completed. |
NEEDS_REVIEW | User review is required. |
FAILED | Processing failed and the failure is persisted. |
APPROVED | User approved the classified/parsed result. |
MATERIALIZED | Business records were created or linked from the approved output. |
Redis choreography convention
Redis infrastructure already exists in backend/shared/redisBus.js.
Use Redis Streams for guaranteed pipeline advancement:
RedisBus.xaddJsonRedisBus.consumeLoop- consumer groups
xAckonly after the DB state update succeeds
Use Pub/Sub only for non-critical notifications and UI/admin observability.
The shared naming convention is defined in:
backend/shared/documentProcessingEvents.js
Command and control API
document-indexer-service exposes command/control endpoints. These endpoints
update document_processing_runs and publish Redis stream events. They do not
execute the whole pipeline synchronously.
| Method | Path | Purpose |
|---|---|---|
POST | /documents/:id/process | Create a new processing run and publish document.processing.requested. |
POST | /documents/:id/retry | Retry the latest failed run, or a specific failed run when runId is passed. |
POST | /documents/:id/reset | Reset the latest run to REQUESTED, or create one if missing. |
GET | /documents/:id/processing-status | Return the latest processing run for a document. |
PATCH | /documents/:id/processing-result | Update the latest processing run with classification/parser output. |
GET | /processing-runs | List processing runs, optionally filtered by document_id and status. |
GET | /processing-runs/:id | Return one processing run. |
PATCH | /processing-runs/:id/result | Update one processing run with classification/parser output. |
docs-service owns text extraction and OCR through:
| Method | Path | Purpose |
|---|---|---|
POST | /docs/:id/ensure-text | Return existing valid document_texts or extract/OCR and persist text. |
For cloud-origin files that are not yet copied into rAInty storage,
ensure-text retrieves the file through storage-connector-service using the
document source reference (source_connection_id and source_file_id). Inline
contentBase64 remains supported only for legacy/manual callers; cloud sync
does not use it.
Entry point handoff
The three document ingress paths now hand off to the same pipeline state:
- Manual upload:
docs-service /docs/uploadstores the document, ensures text, then callsdocument-indexer-service /documents/:id/process. - Email: mail actions upload through
docs-service /docs/uploadwithsource_type=EMAIL; the upload endpoint triggers the pipeline. - Cloud sync:
storage-connector-servicesends only source metadata todocument-indexer-service /index/storage-file; that worker creates or links thedocumentsrow and creates a pipeline run. The text extraction consumer later retrieves the provider file on demand throughdocs-service.
Guaranteed stream:
<env>:document:processing
Events:
document.processing.requesteddocument.text.extraction.requesteddocument.text.readydocument.classification.requesteddocument.classifieddocument.parsing.requesteddocument.parseddocument.indexing.requesteddocument.indexeddocument.review.requireddocument.materialization.requesteddocument.materializeddocument.processing.faileddocument.processing.completed
Each choreography step must:
- read
document_processing_runs; - verify the expected state;
- skip idempotently when the state is no longer valid;
- do its own work;
- update
status,current_step, timestamps, and output JSON; - publish the next event through
RedisBus.xaddJson; - persist
FAILEDwitherror_messageandattempt_counton error.
Implemented consumers:
| Service | Consumer group | Events handled |
|---|---|---|
docs-service | document-processing:text-extraction | document.text.extraction.requested |
document-indexer-service | document-processing:pipeline-router | document.processing.requested, document.text.ready, document.classification.requested, document.classified, document.parsing.requested, document.parsed, document.indexing.requested |
The current classification worker is deterministic and conservative. It uses
the existing documents.document_type metadata when available, otherwise it
classifies the document as OTHER. The parser step currently handles
UTILITY_BILL and CONDOMINIUM_INSTALLMENTS through llm-gateway, using
persisted document_texts as input. Other document types continue directly to
indexing or review.
Failure handling:
- the worker updates the run to
FAILED; failed_stepstores the failing step name;error_messagestores the error;attempt_countis incremented;document.processing.failedis published for observability.
Redis is only the advancement mechanism. The canonical state remains in
document_processing_runs, so events can be skipped idempotently when a run has
already advanced or has been reset/retried.