Skip to main content

✅ Document Validation

Overview

Automatic quality control on documents entering DocuDesk: a fixed catalogue of file-level checks (format, integrity, encryption, text-layer presence) and a record-level check (metadata completeness), configured through per-document-type validation profiles, producing a validationStatus verdict and validationFindings[] on the document record via the ADR-031 calculation pattern (computation backend service + x-openregister-calculations, mirroring metadata enrichment).

Validation judges and never mutates — enrichment (deriving values) stays with metadata enrichment, object-shape validation stays with OpenRegister schema validation, virus scanning stays with OpenRegister file attachments. Defaults are warn-only; blocking intake is an explicit per-check admin opt-in.

It closes the "zero entities because zero text" silent-failure mode: a scan-only PDF with no text layer yields zero detected entities, which an operator could mistake for "nothing to redact" and publish PII.

Check catalogue

checkIdFires when
format-not-allowedThe file mime type is not in the profile's allowlist.
extension-mime-mismatchThe file extension contradicts the detected content type.
file-unreadableThe file could not be read/parsed.
pdf-encryptedThe PDF is encrypted/password-protected (cannot be anonymised).
text-layer-missingA page-bearing format yields fewer than docudesk.validation.text_layer_min_chars_per_page (default 32) extractable chars/page. Carries suggestedAction: "ocr".
metadata-incompleteA required metadata field for the profile is absent/empty. Names the field.

A finding contains only checkId, severity, a localised message (+ params), optional field, and optional suggestedAction — never document content.

Verdict aggregation

validationStatus aggregates findings: any blocking finding → failed; otherwise any warningwarnings; otherwise passed. Records never validated render as not yet validated (absent value); there is no backfill migration.

Profiles

Profiles live in app config docudesk.validation.profiles (JSON):

{
"default": {
"allowedMimes": ["application/pdf", "text/plain"],
"requiredFields": [],
"severities": { "pdf-encrypted": "warning" }
},
"factuur": {
"allowedMimes": ["application/pdf"],
"requiredFields": ["invoiceNumber"],
"severities": { "pdf-encrypted": "blocking" }
}
}

Per document type: an allowed-mime list, required metadata fields, and a severity per check (off | warning | blocking). Unknown document types resolve to the default profile. Shipped defaults set every check to warning (no blocking out of the box). Profile reads happen at validation time, so config changes propagate without a restart.

On-demand endpoint

POST /apps/docudesk/api/validation/validate

{ "fileId": 42, "documentType": "factuur" }

Response (200):

{
"validationStatus": "warnings",
"validationFindings": [
{ "checkId": "pdf-encrypted", "severity": "warning", "message": "…", "params": {} }
]
}

#[NoAdminRequired]; the file is resolved through the requesting user's folder (404 when not resolvable, no existence disclosure — IDOR-safe per ADR-005). The endpoint computes findings without persisting anything.

Stored verdict (calculation)

validationStatus and validationFindings are declared as x-openregister-calculations on the generatedDocument schema in docudesk_register.json, with DocumentValidationService (backend docudesk.validation) as the computation backend. Until OpenRegister's ADR-031 calculation runtime invokes the service directly, the DocuDesk event-listener fallback (ValidationRunner) computes and stores the verdict on object create/update. The listener contains no validation logic.

Configuration

KeyDefaultMeaning
docudesk.validation.profiles{} (defaults apply)Per-type validation profiles.
docudesk.validation.text_layer_min_chars_per_page32Text-layer threshold.

Full-text search across documents is OpenRegister's domain (ADR-022) and is surfaced through Nextcloud's unified search — DocuDesk does not ship a separate search integration. (The previous Apache Solr document was removed; no Solr integration exists in the codebase.)