Attachment System
Status: Implemented Depends On: Page System, Permission System
Overview
Section titled “Overview”The Attachment System manages file uploads (images, PDFs, documents) stored alongside workspace content. Files are stored on the local filesystem with metadata tracked in SQLite. The system provides content-addressable deduplication via SHA-256, extension-based allowlist security, quota enforcement, and reference tracking to prevent orphaned files.
Attachments are the storage backbone for both the dedicated Image Block content type and inline file references.
Upload Flow
Section titled “Upload Flow”Architecture
Section titled “Architecture”Framework (Tauri) └── Commands apps/desktop/src-tauri/src/commands/ upload_attachment, upload_attachment_bytes, get_attachment, get_attachment_file, list_attachments, delete_attachment
Application ├── UploadAttachmentUseCase crates/application/src/attachment/upload.rs │ SHA-256 hash, dedup, quota check, magic-byte validation, file write ├── DeleteAttachmentUseCase crates/application/src/attachment/delete.rs │ Reference guard, metadata-first deletion, variant cache cleanup ├── BulkDeleteAttachmentsUseCase crates/application/src/attachment/bulk_delete.rs ├── GetAttachmentUseCase crates/application/src/attachment/get.rs ├── GetAttachmentFileUseCase crates/application/src/attachment/get_file.rs ├── GetImageVariantUseCase crates/application/src/attachment/get_variant.rs ├── ListAttachmentsUseCase crates/application/src/attachment/list.rs ├── ListAttachmentsWithRefsUseCase crates/application/src/attachment/list_with_refs.rs ├── GetStorageUsageUseCase crates/application/src/attachment/get_storage_usage.rs ├── CheckStorageQuotaUseCase crates/application/src/attachment/check_quota.rs ├── UpdateAttachmentReferencesUseCase crates/application/src/attachment/update_references.rs ├── EnqueueAttachmentSyncUseCase crates/application/src/attachment/enqueue_sync.rs ├── DownloadRemoteAttachmentUseCase crates/application/src/attachment/download_attachment.rs ├── AttachmentSyncCoordinator crates/application/src/attachment/sync_coordinator.rs ├── AttachmentRepository (trait) crates/application/src/attachment/services.rs ├── ImageProcessingProvider (trait) crates/application/src/attachment/services.rs └── AttachmentSyncProvider (trait) crates/application/src/attachment/services.rs
Domain ├── Attachment crates/domain/src/attachment.rs ├── AttachmentReference crates/domain/src/attachment.rs ├── ContentType crates/domain/src/attachment.rs ├── AttachmentSyncStatus crates/domain/src/attachment.rs ├── ImageVariant crates/domain/src/attachment.rs ├── StorageQuota crates/domain/src/attachment.rs ├── StorageUsage crates/domain/src/attachment.rs └── AttachmentSummary crates/domain/src/attachment.rs
Infrastructure └── SqliteAttachmentRepository crates/infrastructure/sqlite/src/workspace/attachment_repository.rsStorage Layout
Section titled “Storage Layout”~/Inklings/Workspaces/MyVault/├── .inklings/inklings.db # Attachment metadata (attachments + attachment_references tables)└── attachments/ ├── {uuid}.png # Original file: {attachment_id}.{ext} ├── {uuid}.pdf ├── {uuid} # Files with no extension (e.g., dotfiles) └── .cache/ ├── {uuid}_thumb.webp # 200px thumbnail variant └── {uuid}_display.webp # 2000px display-optimized variantFiles are named {attachment_id}.{extension} for direct filesystem access. The .cache/ subdirectory holds generated
image variants (WebP format).
Database Schema
Section titled “Database Schema”CREATE TABLE attachments ( id TEXT PRIMARY KEY, original_filename TEXT NOT NULL, file_extension TEXT NOT NULL, content_type TEXT NOT NULL, -- MIME type string (human-readable) size_bytes INTEGER NOT NULL, content_hash TEXT NOT NULL, -- SHA-256 hex digest sync_status TEXT NOT NULL DEFAULT 'local_only', created_at TEXT NOT NULL, updated_at TEXT NOT NULL);
CREATE UNIQUE INDEX idx_attachments_hash ON attachments(content_hash);CREATE INDEX idx_attachments_sync_status ON attachments(sync_status);CREATE INDEX idx_attachments_pending_sync ON attachments(sync_status) WHERE sync_status IN ('local_only', 'failed');
CREATE TABLE attachment_references ( attachment_id TEXT NOT NULL, page_id TEXT NOT NULL, block_id TEXT, FOREIGN KEY (attachment_id) REFERENCES attachments(id) ON DELETE CASCADE, FOREIGN KEY (page_id) REFERENCES pages(id) ON DELETE CASCADE, PRIMARY KEY (attachment_id, page_id));
CREATE INDEX idx_attachment_refs_page ON attachment_references(page_id);The content_type column stores the MIME type string for human readability, but reconstruction uses
ContentType::from_extension() on the file_extension column to avoid round-trip mismatches.
Key Design Decisions
Section titled “Key Design Decisions”1. Content-Addressable Deduplication
Section titled “1. Content-Addressable Deduplication”Every upload computes a SHA-256 hash of the file bytes. Before writing anything to disk, the system checks
attachments.content_hash for an existing match. If found, the existing Attachment is returned immediately — no disk
write, no new metadata row. This is transparent to the caller.
2. Extension-Based Allowlist
Section titled “2. Extension-Based Allowlist”The domain layer enforces a security allowlist of permitted file extensions:
Images: png, jpg, jpeg, gif, webp, svgDocuments: pdf, txt, mdOffice: doc, docx, xls, xlsx, ppt, pptx, odt, ods, csv, rtfUnknown extensions are rejected at the domain Attachment::new() constructor. Empty extensions (e.g., Makefile,
.gitignore) are allowed.
3. Magic-Byte Validation
Section titled “3. Magic-Byte Validation”For image uploads, the infer crate validates that file content matches the declared extension. A mismatch (e.g., a
.png extension on a JPEG file) is rejected with an error. For non-image types (office docs, PDFs), mismatches are
logged as warnings but not blocked, since these formats have complex or optional magic byte signatures.
4. Dual Quota Enforcement
Section titled “4. Dual Quota Enforcement”Two quotas are checked before writing to disk (fail-fast ordering):
| Quota | Default | Check Order |
|---|---|---|
| Per-file size limit | 10 MB | First (before hashing) |
| Workspace total limit | 100 MB | Second (after dedup check) |
The workspace total check runs after the dedup lookup — if the content already exists, the quota is irrelevant.
5. Metadata-First Deletion
Section titled “5. Metadata-First Deletion”Following the multi-DB transaction pattern, deletion order is: metadata first, file second. If metadata deletion
succeeds but the file is already missing from disk, the operation still succeeds gracefully. Cached image variants
(.cache/{id}_thumb.webp, .cache/{id}_display.webp) are cleaned up as well.
6. Reference Guard
Section titled “6. Reference Guard”Single-attachment deletion is blocked if any page still references the attachment. The get_references_for_attachment()
check prevents orphaning content. The BulkDeleteAttachmentsUseCase with force: true overrides this guard for cleanup
operations.
7. Capability-Gated Access
Section titled “7. Capability-Gated Access”All write operations require Capability::AttachmentsWrite. Read operations require Capability::AttachmentsRead. The
permission guard is checked at the start of every use case.
Image Variants
Section titled “Image Variants”The system defines two on-device optimization variants:
| Variant | Max Dimension | Format | Use Case |
|---|---|---|---|
Thumbnail | 200px | WebP | Grid views, navigation |
DisplayOptimized | 2000px | WebP | In-editor display |
Variants are generated lazily via GetImageVariantUseCase using the ImageProcessingProvider trait. Generated files
are cached in attachments/.cache/ and cleaned up on attachment deletion.
Sync Architecture
Section titled “Sync Architecture”Attachments participate in cloud sync via three components:
- AttachmentSyncStatus: Tracks per-attachment state (
LocalOnly->Synced, withRemoteOnly,Downloading,Failedstates). - AttachmentSyncProvider (trait): Cloud storage operations (upload/download bytes, metadata push/pull). Infrastructure implements this with Supabase Storage.
- AttachmentSyncCoordinator: Orchestrates sync — enqueues uploads after local saves, handles downloads for remote-only attachments.
- attachment_sync_queue table: Durable queue for pending uploads with retry tracking.
Stale downloads (status stuck in Downloading after a crash) are reset to RemoteOnly on workspace open via
reset_stale_downloads().
Key Code Paths
Section titled “Key Code Paths”| Scenario | Entry Point |
|---|---|
| File upload (path-based) | UploadAttachmentUseCase::execute() |
| File upload (raw bytes) | Same use case, bytes passed directly |
| Dedup check | AttachmentRepository::get_by_hash() |
| Delete with reference guard | DeleteAttachmentUseCase::execute() |
| Reference tracking | UpdateAttachmentReferencesUseCase::execute() |
| Image variant generation | GetImageVariantUseCase::execute() |
| Storage usage query | GetStorageUsageUseCase::execute() |
Stores and manages file attachments. See also: Block Content System (Image blocks reference attachments), Page System.
Was this page helpful?
Thanks for your feedback!