This document explains FRAME's storage architecture, including receipt logs, state storage, transaction journals, and crash safety mechanisms.
FRAME uses a multi-layer storage architecture:
- Receipt Logs: Append-only receipt storage (Rust backend)
- State Storage: Canonical JSON key-value storage (Tauri backend)
- Transaction Journal: In-flight transaction tracking (per-identity)
- Event Logs: System event logging with rotation
All storage is local-first and encrypted at rest.
Receipts are stored in an append-only log file with frame-based format:
File: storage/chain_<identity>.log
Frame Structure:
[u32 length (big-endian)]
[u32 checksum (big-endian)]
[JSON receipt payload (UTF-8)]
Example:
[00 00 01 2C] // Length: 300 bytes
[12 34 56 78] // Checksum
{ "version": 2, "timestamp": 1234567890, ... } // JSON receipt
Append Receipt (receipt_append_internal):
- Serialize receipt to canonical JSON
- Compute checksum (hash of JSON bytes)
- Build frame: length + checksum + JSON
- Write to temporary file
fsync()to ensure durability- Atomic rename to log file
Read Receipts (receipt_read_all):
- Read log file
- Parse frames sequentially
- Validate checksums
- Return array of receipts
Crash Safety:
- Writes to temporary file first
fsync()ensures data is on disk- Atomic rename prevents partial writes
- Checksums detect corruption
State storage provides canonical JSON key-value storage:
Backend: Tauri storage_read / storage_write commands
Location: storage/<key>.enc (encrypted files)
Validation: All writes validated for canonical JSON
File: ui/runtime/storage.js
Rules:
- Only canonical JSON allowed:
- Strings, booleans, safe integers, null
- Arrays, plain objects
- Rejected types:
- Floating point numbers (only safe integers)
- NaN, Infinity
- undefined
- Functions
- Date, RegExp, Map, Set, etc.
- Circular references
Validation Function:
function validateCanonicalJson(value, path, seen) {
// Recursively validate value
// Throw error if non-canonical
}Read:
await window.FRAME_STORAGE.read(key)
// Returns: value (deep-frozen) or nullWrite:
await window.FRAME_STORAGE.write(key, value)
// Validates canonical JSON
// Writes to encrypted storage
// Deep-freezes valueStorage Keys:
chain:<identity>: Receipt chain (legacy format, migrated to log)permissions:<identity>: Capability grantsstorage:<key>: Application stateframe_*: System state (excluded from state root)
The transaction journal tracks in-flight transactions for atomic commits:
File: storage/journal:<identity>.log
Purpose: Ensure atomic state updates with receipt appends
Format: Lightweight log of pending writes
Process:
- Before state write: Record in journal
- Write state: Update storage
- Append receipt: Write to receipt log
- Commit: Remove journal entry
- On crash: Replay journal entries
Journal Entry:
{
key: "storage:wallet:balance",
value: 100,
timestamp: 1234567890
}Event logs are rotated to prevent unbounded growth:
File: storage/event.log
Rotation:
- When log exceeds size limit (e.g., 10MB)
- Rotate:
event.log→event.log.1 - Previous:
event.log.1→event.log.2 - Keep last N rotated logs
Format: JSON lines (one event per line)
Events:
- Intent executions
- Receipt creations
- State updates
- Errors
Files: src-tauri/src/lib.rs, src-tauri/src/receipt_log.rs
Responsibilities:
- Encrypted file storage
- Receipt log management
- Identity vault management
- Cryptographic operations
Storage Location (platform-specific):
- Linux:
~/.local/share/frame/ - macOS:
~/Library/Application Support/frame/ - Windows:
%APPDATA%/frame/
Directory Structure:
frame/
├── identities/
│ └── <identity_id>/
│ ├── keys/
│ │ └── ed25519_keypair.enc
│ └── storage/
│ ├── chain_<identity>.log
│ ├── journal_<identity>.log
│ ├── event.log
│ └── <key>.enc
└── .storage_key
File: ui/runtime/storage.js
Responsibilities:
- Canonical JSON validation
- Storage abstraction
- Deep-freezing values
- Wrapping Tauri backend
Interface:
window.FRAME_STORAGE = {
read: async (key) => { /* ... */ },
write: async (key, value) => { /* ... */ }
}Receipt chains are stored in two formats:
Key: chain:<identity>
Format: Array of receipt objects
Location: storage/chain_<identity>.enc
Migration: Automatically migrated to log format on first write
File: storage/chain_<identity>.log
Format: Frame-based append-only log
Advantages:
- Crash-safe appends
- Efficient sequential reads
- Checksum validation
- No full-chain rewrites
Migration Process:
- Read legacy array format
- Write each receipt to log file
- Verify checksums
- Remove legacy file
State root includes storage state:
Components:
- Identity public key
- Installed dApps (with code hashes)
- Storage keys/values (canonicalized)
- Receipt chain commitment
Excluded Prefixes:
chain:(included via commitment)frame_integrityframe_context_frame_layoutframe_widgetsframe_capsulesframe_logsframe_ui
Computation:
var stateRoot = {
version: STATE_ROOT_VERSION,
capabilityVersion: CAPABILITY_VERSION,
identityPublicKey: publicKey,
installedDApps: dapps, // Sorted by id
storage: storage, // Sorted keys
receiptChainCommitment: commitment
};
var hash = sha256(JSON.stringify(canonicalize(stateRoot)));FRAME ensures crash safety through:
Receipt Log:
- Write to temporary file
fsync()to disk- Atomic rename
State Storage:
- Write to temporary file
fsync()to disk- Atomic rename
Tracks in-flight transactions:
- Records pending writes
- Replays on recovery
- Ensures atomicity
Receipt log frames include checksums:
- Detects corruption
- Validates integrity
- Enables recovery
FRAME supports migration from legacy formats:
From: Array format (chain:<identity>)
To: Log format (chain_<identity>.log)
Process:
- Detect legacy format
- Read all receipts
- Write to log file
- Verify integrity
- Remove legacy file
From: Unencrypted storage
To: Encrypted storage
Process:
- Read unencrypted keys
- Encrypt with storage key
- Write to encrypted files
- Remove unencrypted files
All storage is encrypted at rest:
Storage Key: Generated per-installation, stored in .storage_key
Encryption: AES-GCM with 256-bit keys
Key Derivation: From master key + identity ID
Storage access controlled by:
- Identity isolation (per-identity storage)
- Capability grants (dApp permissions)
- Runtime validation (canonical JSON only)
Storage integrity ensured by:
- Checksums (receipt log)
- State root verification
- Code hash verification
- Receipt chain linking
- Receipt Chain Commitment: Rolling hash instead of full chain
- State Root Caching: Cache computed state roots
- Lazy Loading: Load receipts on demand
- Batch Writes: Group storage writes
- Receipt Log: Unbounded (append-only)
- State Storage: Unbounded (per-key)
- Event Log: Rotated at size limit
- Transaction Journal: Cleared after commit
// Read
var value = await window.FRAME_STORAGE.read(key);
// Write
await window.FRAME_STORAGE.write(key, value);
// Receipt operations (via Tauri)
await window.__FRAME_INVOKE__('receipt_append', { identity, receipt });
var receipts = await window.__FRAME_INVOKE__('receipt_read_all', { identity });// Append receipt
pub fn receipt_append_internal(
app: &AppHandle,
identity_id: &str,
receipt: &serde_json::Value,
) -> Result<(), String>
// Read receipts
pub fn receipt_read_all(
app: &AppHandle,
identity_id: &str,
) -> Result<Vec<serde_json::Value>, String>FRAME storage provides:
- Crash Safety: Atomic writes, checksums, journal recovery
- Integrity: Canonical JSON validation, state root verification
- Isolation: Per-identity storage, capability-scoped access
- Replay Safety: Receipt chain enables full state reconstruction
- Encryption: All data encrypted at rest
These guarantees ensure that FRAME can reconstruct entire application sessions from receipt chains, even after crashes or corruption.