02 — Storage Model
Purpose
Separate where a file exists, what role that location serves, cloud synchronization state, and backup protection. These concepts must never be collapsed into one status.
Location Types
- INTERNAL_VOLUME
- EXTERNAL_VOLUME
- ICLOUD_DRIVE
- ONEDRIVE
- OTHER_LOCAL
- FUTURE_NETWORK
- FUTURE_CLOUD
A OneDrive or iCloud item may also physically consume internal disk space. Provider classification and physical consumption are separate attributes.
StorageClassifier
StorageClassifier (BurrowEngine) classifies one inventory record without walking the filesystem or writing files. It reuses VolumeClassification for volume-flag types and adds provider location types.
| Provider type | Location type | Cloud state | 0.1 protection | Allocation |
|---|---|---|---|---|
LOCAL on internal, non-removable |
INTERNAL_VOLUME |
NOT_CLOUD |
LOCAL_ONLY |
pass-through |
LOCAL on removable, not internal |
EXTERNAL_VOLUME |
NOT_CLOUD |
LOCAL_ONLY |
pass-through |
LOCAL any other volume flags |
OTHER_LOCAL |
NOT_CLOUD |
LOCAL_ONLY |
pass-through |
ICLOUD (including an item stored locally) |
ICLOUD_DRIVE |
observed, else CLOUD_UNKNOWN |
SYNCED_ONLY |
pass-through |
ONEDRIVE |
ONEDRIVE |
observed, else CLOUD_UNKNOWN |
SYNCED_ONLY |
pass-through |
An iCloud item that occupies internal disk is still ICLOUD_DRIVE. Its allocated_size_bytes stay on the record and are not replaced by the provider type. LOCAL records ignore an observed cloud hint so internal-only files stay NOT_CLOUD / LOCAL.
SYNCED_LOCAL (cloud state) is not BACKED_UP (protection). Release 0.1 emits only LOCAL_ONLY, SYNCED_ONLY, and UNKNOWN.
Storage Roles
ACTIVE
Working data expected to remain readily accessible.
ARCHIVE
Preserved historical data that does not need to remain in active storage.
BACKUP
A secondary copy maintained for recovery.
A volume may support multiple roles, but each configured root should have an explicit intended role.
Cloud State
Where detectable:
- NOT_CLOUD
- CLOUD_UNKNOWN
- CLOUD_ONLY
- DOWNLOADED
- PINNED_LOCAL
- SYNCED_LOCAL
- ERROR_OR_CONFLICT
Provider-specific details should be retained separately from normalized state.
Protection State
Derived, not manually asserted:
- UNPROTECTED
- LOCAL_ONLY
- SYNCED_ONLY
- BACKED_UP
- VERIFIED_BACKUP
- ARCHIVED
- MULTI_LOCATION_PROTECTED
- UNKNOWN
The derivation algorithm must be documented and testable.
0.1 derivation (authoritative)
StorageClassifier.protectionState(forProviderType:) reuses StorageProtectionState. It is the only 0.1 derivation. Catalog and UI labels come from StorageProtectionState.catalogLabel (web: catalogProtectionLabel).
| Provider type | 0.1 protection | Catalog/UI label |
|---|---|---|
LOCAL (including an empty token) |
LOCAL_ONLY |
Local only |
ICLOUD, ICLOUD_DRIVE |
SYNCED_ONLY |
Synced only |
ONEDRIVE, ONE_DRIVE |
SYNCED_ONLY |
Synced only |
| Any other token | UNKNOWN |
Unknown |
The function never returns BACKED_UP, VERIFIED_BACKUP, UNPROTECTED, ARCHIVED, or MULTI_LOCATION_PROTECTED. Labels never say “backed up” or “protected” for a synced cloud file. Those later-release states remain listed above as future work. The 0.1 constraint is authoritative.
Release 0.1 may only emit states Burrow™ can prove without backup functionality: LOCAL_ONLY, SYNCED_ONLY, and UNKNOWN. Do not describe synchronized cloud files as backed up or protected. BACKED_UP, VERIFIED_BACKUP, ARCHIVED, MULTI_LOCATION_PROTECTED, and UNPROTECTED remain later-release states.
Collection
A Collection is a logical group of data for backup/health purposes, e.g.:
- Personal Documents
- Development
- Photos
- Financial
- Archive
Collections reference one or more roots and later one or more protection policies.
Stable Identity
Paths are mutable. Track:
- volume stable identifier;
- filesystem identity where available;
- current path;
- content hash where calculated.
Do not assume path alone permanently identifies a file.
Logical vs Physical Size
Capture both when possible:
- logical file size;
- allocated/physical size.
Cloud placeholders may have a large logical size and minimal local allocation. The UI must not misleadingly count the logical size as immediately reclaimable local disk space. Inventory persists both fields on file_item and treats CLOUD_ONLY logical size as catalog metadata, not locally allocated reclaimable bytes.
The engine persists both columns on file_item (logical_size_bytes, allocated_size_bytes) when a read-only probe can observe them. Query results expose both fields when known. scan.logical_bytes_seen is inventory progress, not local reclaim.
Public size types: FileSizeObservation, FileSizeWrite, FileSizeRecord, FileSizeProbe, ReclaimableLocalBytes. Catalog.persistFileSizes writes the size pair; Catalog.fileSizes() / Catalog.fileSize(fileId:) return it.
Catalog.storageAnalytics() (VLT-32) is the read-only Storage View query: capacity/utilization, largest files/folders, age buckets, file-type summaries, cloud vs local allocation, and scan timestamp/age. It reuses ReclaimableLocalBytes and StorageClassifier. Duplicate recovery is unavailable_until_0_2. There is no HTTP analytics route and no recommendation labeled safe to delete. The 0.1 dashboard binds a typed fixture payload (DashboardAnalyticsPayload) with the same allocated-bytes rule until catalog numbers are supplied.
Reclaimable Space
"Potential recovery" must be conservative.
ReclaimableLocalBytes (and FileSizeRecord.reclaimableLocalBytes) uses allocated size only. If allocated size is unknown, the local estimate is 0 — never the logical placeholder size. A 5 GB cloud placeholder that occupies 4 KB locally contributes 4 KB, not 5 GB.
For exact duplicates, do not sum all copies. A duplicate group with N copies can at most reclaim the size of the copies that are genuinely removable. That N-1 math is release 0.2 and is not implemented here.
Cloud eviction ("free up local space") is different from deletion and should be presented as a separate future action.
Source of Truth
Filesystem/provider state observed during the latest scan is evidence. The SQLite catalog is historical state and analysis.
The UI should display scan age so users know when a conclusion may be stale.