Architecture
How Qvault keeps documents on your machine, how detection and export work, and what it does not (yet) do. This page describes the current design; the detailed engineering notes are in docs/qvac-integration.md in the repository.
Overview
Qvault is a Tauri v2 desktop application. The interface is React and TypeScript rendered in the operating system’s own webview (WebKit on macOS and Linux, WebView2 on Windows). Everything that touches document content runs in a Rust backend in the same process; the interface talks to it through Tauri’s in-process command bridge, not through HTTP.
The optional AI features (Layer 3 and OCR) run in a second, bundled process: the QVAC sidecar, built on Tether’s QVAC SDK and the Bare JavaScript runtime.
Document pipeline
- Extraction. PDFs are rendered with PDF.js, which also provides each piece of text with its position on the page. DOCX files are read as the ZIP of XML parts they are: the main document, headers, footers, footnotes, endnotes and comments, with tracked changes resolved so that the scanner and the redactor see exactly the same text.
- OCR. Pages without a text layer, and PNG, JPG and TIFF images, are rendered to images and sent to the sidecar for OCR. Pages count as scanned when they have fewer than 32 visible characters or images cover at least half the page; they are rendered with PDF.js at 200 DPI by default (configurable). The recognised words and their boxes are used for detection, the review highlights and placing redaction boxes. They are never written into an exported file: redacted scanned pages are burned in and re-checked with OCR, and no invisible text layer is added.
- Detection. Layers 1 and 2 always run; Layer 3 runs if it is enabled and a detector model is installed.
- Review. Findings are stored with their category, confidence, source layer and status (pending, approved, rejected). Nothing is applied until you approve it.
- Export. Approved redactions are applied and the result is verified before it is kept.
Detection layers
Layer 1: patterns
Compiled regular expressions (Rust’s regex crate, linear-time, no backtracking) for structured data. Matches get a confidence of 0.95. A priority order prevents double matches, such as a phone number inside a URL.
| Set | Examples |
|---|---|
| Global | Email, phone numbers, credit cards, IBAN, IPv4 addresses, URLs, monetary amounts, dates |
| United States | Social Security numbers, EINs |
| European Union | VAT identification numbers |
| Brazil | CPF, CNPJ, CEP |
| Germany | Steuernummer |
Layer 2: heuristics
Rules for names, which no pattern describes: company names with legal suffixes (LLC, Inc, Corp, GmbH, SA, SARL, LTDA, EIRELI and others), names next to titles and roles (“John Smith, Manager”, “representada por…”), names in ownership tables and entity descriptions, and addresses. Name connectors such as de, da, dos, van, von, del are understood. Lists of stop words and stop phrases (“Section”, “Agreement”, “Limited Liability”…) keep legal vocabulary from being flagged. Confidence ranges from 0.75 to 0.92 depending on the evidence.
Layer 3: local AI
Layer 3 has two detectors. The default is OpenAI Privacy Filter (Apache-2.0), a token classifier built for finding personal data, run through QVAC’s ONNX add-on (@qvac/onnx) on the CPU. Qvault tokenizes each page in Rust, the sidecar returns the model’s raw scores for every token, and Qvault decodes them into spans with a port of OpenAI’s reference Viterbi decoder, so the spans match OpenAI’s own code on the same scores. The model reports a catch-all “account number” label, which Qvault re-types using the Layer 1 validators (CPF, CNPJ, NIF, IBAN and others) and nearby label words; otherwise it stays a bank account at low confidence. Filters drop titles in front of names, parties in case-law citations, statute numbers and legal words, using rules designed from evaluation errors. A page takes about 0.2 seconds and about 2 GB of memory. Privacy Filter does not look for company names, amounts or case numbers; Layers 1–2 cover those.
The second detector is a small language model, used for an optional thorough pass after Privacy Filter (or instead of it). Where the two detectors overlap, the longer span is kept. Every AI finding is labelled in the review list with the detector that found it.
For the language model, the text is split into chunks of about 150 words with 20 words of overlap. For each chunk the model is asked, with a fixed prompt and a JSON schema that constrains its output, to list personal data by category. Qvault then validates every answer (shape, known category, plausibility rules that reject whole sentences), finds every occurrence of each returned string in the chunk on word boundaries, drops anything Layers 1–2 already covered, removes legal vocabulary with a stop-list, and adds the rest for review. Confidence depends on the category and comes from how often the model’s additions were right in the evaluation: 0.80 for names, ID numbers, bank details and dates of birth, down to 0.50 for addresses and case numbers. Generation is deterministic (temperature 0, fixed seed, and the “thinking” mode of Qwen3 turned off), so the same document gives the same suggestions. Results appear page by page as they are found.
The recommended language model is Qwen3-1.7B (Q8_0, about 1.8 GB download, about 2.75 GB of memory). Qwen3 0.6B is offered for computers with little memory, and Qwen3 4B and 8B for workstations; the older Qwen2.5 models remain for existing installs. The Model Manager shows each model’s memory need and checks whether it fits before it can be selected. A chunk that fails is skipped. If Privacy Filter cannot run, the scan uses the language model if one is installed; if the sidecar is unavailable, Layer 3 stops for that scan and the viewer says so. The scan always completes with Layers 1–2.
Entity groups
Detected names, companies, numbers and addresses are collected into entities across all your documents, and spellings of the same entity are grouped. Entities of different categories are never compared.
- Applied automatically only when the match is unambiguous: the same name after normalising case, accents, titles, spacing and “Last, First” order; a company with or without its legal form; the same number with other separators or a country code.
- Proposed for review: initials or a surname alone, a near-identical spelling, a partial company name or acronym, most of an address, and anything found by similarity. Proposals where the short form also fits another person are marked ambiguous. You accept or dismiss each one, and you can merge or split groups by hand.
- Known-entity suggestions. After a scan, Qvault looks for other spellings of known entities that no layer caught and adds them as pending findings (source “known entity”, confidence 0.40–0.60) with the reason shown. They are never approved automatically.
- Optional embeddings. With the Nomic Embed Text v2 model installed, similar-meaning variants are proposed too; vectors are stored in the local database. Without the model, everything above still works on rules alone. On our small synthetic test set (59 entities, 199 mentions in English, Spanish and Portuguese), no automatic merge was wrong, about two in three proposals were correct, and the embedding model did not change the result.
Two different people written identically (for example “J. Santos”) cannot be told apart and end up in one group; nicknames such as “Bob” and “Robert” are not linked.
The QVAC sidecar
QVAC’s inference packages run on Bare, a small JavaScript runtime. Qvault bundles the Bare executable and a short worker script with the app and starts it only when an AI feature is first used. Design choices:
- Standard input/output only. Requests and responses are JSON lines over the child process’s pipes. The sidecar opens no HTTP server and no sockets; this was checked with
lsofand by running it under a sandbox that denies all network access. - A fixed allow-list of methods: load and unload a model, OCR, completion, embedding, token classification, a memory-fit check, cancel and shutdown. Anything else is rejected.
- Local files only. Model and image paths must be absolute paths to existing files. URLs and QVAC’s registry (which would fetch models over a peer-to-peer network) are refused, and network options of the model runtime are not exposed.
- Supervised. Every request has a timeout and can be cancelled. If the sidecar crashes, it is restarted with backoff, at most three times in five minutes, then marked unavailable for the session. It stops after five minutes idle and on app exit. Its environment is cleared, and document text is never logged.
- Temporary files are wiped. QVAC writes OCR inputs to a temporary folder; Qvault points that folder into its own app data and empties it after every job.
Verified export
Every page with at least one approved redaction is rendered to an image, the black boxes are burned into the pixels in Rust, and the page is replaced by a page that contains only that image. Its old content stream, fonts, form objects and annotations are dropped. Pages without redactions keep their original content. Across the whole file, the document information dictionary, XMP metadata, outlines, forms, name trees (embedded files and scripts), open actions and the structure tree are removed.
Export is refused if any approved redaction could not be placed as a rectangle, if a rasterised page holds more occurrences of an approved string than were covered, or if an approved string appears on a page that is not being redacted (a likely missed detection you should review).
DOCX
Every XML part is parsed and rewritten with a real XML reader and writer, never by raw string replacement. Tracked changes are accepted, so deleted revision text disappears. Within the run-concatenated text of each part, every occurrence of each approved string is replaced; the replacement keeps the formatting of the first run and the rest of the span is removed from the following runs. Alt text, tooltips and field codes get the same treatment. Comment and revision authors are blanked, personal fields in the document properties are emptied, and the thumbnail image is removed.
Verification
After writing, Qvault reads the exported file back, extracts every piece of text it can find, and deletes the file with an error if any approved string survives. The same check runs in the automated test suite.
Storage
Qvault keeps its data in the per-user app data folder: a SQLite database (documents, findings, cross-document entities and their groups, optional entity embeddings, page text and an audit log) and copies of the imported document files. The audit log records actions and metadata, such as model downloads, scans and exports, but not document text. Clearing all data in Settings removes everything.
Network use
Qvault makes no network requests of its own except a model download you start in the Model Manager. That download goes to a pinned, revision-locked Hugging Face URL over HTTPS; the size is shown before you confirm, and the file is checked against a pinned SHA-256 before it is used. There is no telemetry, crash reporting, update check or account. The OCR models are not downloaded by the app at all: you fetch them yourself and import them (see Models and setup).
Limits and open work
- Detection is not perfect. No layer guarantees that all personal data is found. Layer 3 has been measured only on a small synthetic evaluation set (16 English, Spanish and Portuguese legal documents, 461 labels) written by the developers: with Privacy Filter as Layer 3, Layers 1–3 together found about 96% of labelled items and 88% on held-out documents, and about one finding in seven was a false positive, mostly from the Layer 2 name rules. The held-out set is small (60 labels), and the Privacy Filter filters were chosen after its numbers were seen, so treat the held-out figure as optimistic. Real documents will score differently. The set and its reports are in eval/. Always review before sharing.
- OCR quality varies. Recognition errors on poor scans, and punctuation dropped at line breaks (for example in email addresses), can hide data from the detectors. OCR is CPU-bound and takes seconds per page. Only Latin-script text is recognised.
- No encryption at rest. Imported document files, extracted page text and the text of findings are stored unencrypted in the app data folder. Rely on full-disk encryption (FileVault, BitLocker, LUKS) until this is addressed.
- Redacted PDF pages become images. Text on those pages can no longer be selected or searched, and files get larger.
- Platforms. AI and OCR are desktop-only. On Windows, the QVAC runtime needs a recent Vulkan driver even for CPU inference. Installers are not yet code-signed.
- Installer size. Bundling the AI runtime makes the installer several times larger than a build without it (about 53 MB on macOS for Apple Silicon); models are separate.