Provider authors can import a file tree, index its contents and attach local knowledge through
@wirecat/cli-core/documents. Core owns the workflows; a provider supplies authentication,
API translation, native-format conversion and account-bound I/O. No provider SDK is imported by
this module, and importing it opens no database, model or network connection.
| Layer | Location | Responsibility |
|---|---|---|
| Domain | src/documents/ |
Source file/content/change/state contracts, folder scope and catalog selection |
| Services | src/services/documents/ |
Discovery planning, resumable sync execution, ingestion, knowledge and evidence-backed extraction |
| Persistence | Existing public store.notes, store.catalog, store.memories, store.facts APIs |
SQLite transactions and stored entity invariants |
| Readers/models | Existing document readers and model gateway | Text extraction, embedding/search and explicit model execution |
| Provider application | Outside Core | Credentials, vendor errors, API clients, configuration paths, snapshot/checkpoint persistence, locks and CLI/MCP presentation |
src/documents.ts is the public facade. Existing reader exports remain available. Consumers use
that facade rather than internal service or model files. Biome rejects upward imports from the
domain, SQL/transport/vendor imports in services, and private document imports from other modules.
- Normalize remote metadata to
SourceFileand extend it with provider-specific fields as needed. ImplementDocumentSource: identity, folder metadata/children, change cursor, content and bytes. Map vendor folders with the suppliedisFolderpredicate; no Google MIME type is assumed. - Bind
documentIngestionto the public store. Supply the provider/account collection name, external-ID mapping, catalog metadata and content front matter. The optionalobservedcallback registers provider identities/roles. Identity mapping remains the provider's responsibility. - Supply
SourceStatePortfor validated account-specific checkpoints, discovery walks and snapshots. Existing Drive v1 checkpoints remain compatible; no database migration is required. - Acquire the account/profile lock before
syncDocumentsand release it infinally. SupplyassertHeld, provider-safeerrorOf, classification/retry rules and positive bounds. The shared engine retains failed work, advances the cursor only after the batch completes and retires sources leaving the selected tree. The state account must match the selected principal. - Bind
documentKnowledgeto a collection resolver for the selected principal. Every operation checks ownership; notes, facts and memories must also be attached to that document. Replacements cannot reach another document/account. Core's existing fact store owns decision transitions. - For AI extraction, inject a configured
completefunction.extractMemoriesvalidates bounded structured proposals and exact source quotes.extractDocumentMemoriesadds tab selection, preview/save, provenance and source-hash checks before and insidewithWrite. That callback must acquire the same lock used by sync and bind the selected account/document. Results are domain records; the CLI/MCP adapter maps them to its output envelope.
Use a distinct collection namespace per provider/account. External file IDs are scoped by that collection, so two providers can use the same file ID. The application chooses its database path; these services never select the owner's store implicitly. Text changes/removal stale derived memories while retaining owner notes and tags. Accepted facts change only through explicit fact operations.
Service tests use real temporary stores and synthetic source ports with a non-Google folder type. They cover two provider namespaces, interrupted discovery/download, batch continuation, account mismatch, folder scope exit, metadata-only files, optional pictures, annotations and decision history. Extraction tests cover preview, evidence, proposed saves and source changes at both write boundaries. Drive's integration tests exercise its actual adapters against the packed Core candidate. Tests do not contact a real provider or measure model accuracy.
pushDocuments mirrors a local folder one way into a provider's native documents. The provider implements
DocumentTarget: which local paths it takes (kindOf), find-or-create of folders by an origin key, create,
prepare and findByOrigin. It persists PushState through PushStatePort, holds its profile lock and passes
assertHeld, and wraps each write in guard with its permission checks and write journal.
- Unchanged files are skipped by content hash with no remote call.
planPushis the plan alone (dryRun). - A create saves a pending origin key first; the provider stores it on the remote file. After a crash, the next
run finds the file with
findByOriginand updates it instead of creating a second one. preparereads the remote and compares it with the snapshot the last push recorded. It returns a conflict (unlessforce),missing,unchanged(nothing to write) or awritestep; onlywriteruns insideguard, so a conflict never reaches the write journal. The snapshot is the provider's own read-back after its write, so its own push never looks like an edit. One file is one provider write (or one all-or-nothing batch): a write cut in half would read back as an edit.- Nothing is deleted remotely. A file deleted locally is an orphan; a file deleted remotely is missing and is
created again only with
force.
For documents with structure, markdownBlocks (async; the parser loads on first use) parses Markdown into Blocks (headings, paragraphs, bullet,
numbered and checklist items, code, quotes, tables, images, rules) with inline marks. The provider maps its own
document into the same blocks and writes only diffBlocks(current, next), so unchanged paragraphs keep their
comments and formatting. reconcileBlocks(recorded, current, local, lastLocal) allows one remote change, a
checklist tick: a tick follows the side that changed it, judged against lastLocal (the file at the last push), so
a remote tick survives later pushes. Any other remote change is a conflict. The provider's snapshot therefore keeps
both the blocks it wrote and the local blocks it pushed. A field the provider
cannot read back must stay out of its blocks, or that block would differ on every push.
Use previewDocumentTransfer and applyDocumentTransfer from @wirecat/cli-core/store to retain
annotations when a provider changes databases. This persistence workflow lives in
src/store/document-transfer/ and src/store/sqlite/document-transfer/; provider adapters bind the
explicit account, namespace, cached-content checks, profile lock and final checkpoint update.
It contains no provider client, CLI parser or model call.
const options = {
source: "/var/tmp/example-original.db",
destination: "/var/tmp/example-selected.db",
collection: "example-documents:synthetic-owner",
}
const preview = await previewDocumentTransfer(options)
// Present counts, source_documents, missing_bindings and conflicts before applying.
const receipt = await applyDocumentTransfer({
...options,
expected_fingerprint: preview.fingerprint,
})The collection is its exact integration name, such as a provider/account namespace; duplicate names are refused. Preview neither creates nor migrates either file. Source schema 2 through this build's schema is supported without maintenance; upgrade a separate copy of an older source first. Applying initializes or upgrades the explicitly selected destination. File aliases cannot make it the source.
The copy retains document and note revisions, tag/topic assignments and aliases, memory scope/status/ supersession, fact status/confirmation, authors and structured relationships in both directions. Documents match by collection and external key, never title or row number. Different existing content, conflicting topics/aliases and contradictory identity decisions refuse the transfer. Source provider identities can reuse a verified destination person or create a separate person; equal display names never merge people. Only minimal identity/current-association data and author handles are copied, without bot credentials, private provider metadata or complete contact/account history.
Unmatched projects, tasks and other external endpoints appear in missing_bindings. The caller may
supply an explicit bindings object mapping source references to same-kind destination references.
Changes to these decisions or either relevant graph invalidate the fingerprint. Applying rechecks the
plan under the destination write lock and commits the complete bounded copy with its receipt in one
transaction. An interruption rolls back; repeating a completed unchanged copy returns its existing
mapping. The host publishes its checkpoint only after that receipt. Source changes detected during
the copy refuse checkpoint publication, retaining both databases. Stop other writers to the old store
while switching; this is a reviewed snapshot copy, not ongoing bidirectional synchronization.
Limits are 1,000 documents/annotation records, 10,000 relationships or tag assignments, 10,000 revisions per record and a 16 MiB snapshot. Larger graphs refuse without truncation. Original files remain in place. Structured references are remapped; literal references inside free text stay as written. Embeddings and cached pictures are derived and must be indexed again explicitly. The host retains its original refusal guard for store changes that have not completed this operation.