Files
Upload, organize, and recover organization files.
Every file in Siglata belongs to an organization. Files remain with the organization when its members change. The storage architecture incorporates pre-allocated upload reservations, fixed 8 MiB multipart chunking, atomic metadata operations, and an automated 30-day trash recovery lifecycle.
Storage Quota Architecture
Each organization starts with a baseline storage quota of 10 GiB (10,737,418,240 bytes).
Siglata tracks storage through an atomic, transactional ledger that distinguishes between retained data and reserved upload capacity:
┌────────────────────────────────────────────────────────────────────────┐
│ Total Organization Quota (10 GiB) │
├──────────────────────────────────────┬──────────────────┬──────────────┤
│ Used Bytes │ Reserved Bytes │ Available │
│ (Active Files + 30-Day Trash Files) │ (Active Uploads) │ Free Space │
└──────────────────────────────────────┴──────────────────┴──────────────┘
limitBytes: Total storage capacity allocated to the organization (10,737,418,240bytes).usedBytes: Retained storage across both active files and files in trash. Moving an item to trash does not free quota immediately; bytes remain accounted for until the 30-day recovery period lapses and cleanup purges the data.reservedBytes: Storage locked by in-flight multipart uploads. When an upload begins, Siglata reserves the declared file size upfront to prevent concurrent uploads from colliding or exceeding organization limits.- Available Space: Calculated in real time as
limitBytes - usedBytes - reservedBytes.
Multipart Chunking & Upload Reservations
To support massive files with high transfer reliability across unstable connections, Siglata uses a fixed 8 MiB chunking pipeline:
| Parameter | Specification | Value |
|---|---|---|
Chunk Size (CHUNK_SIZE) |
Fixed part size | 8,388,608 bytes (8 MiB) |
| Maximum Part Count | Maximum parts per upload | 10,000 parts |
Maximum File Size (MAX_FILE_BYTES) |
Upper limit per single file | 83,886,080,000 bytes (~80 GB) |
The Upload Lifecycle
1. upload_begin ──> Check Quota ──> Pre-allocate reservedBytes ──> Return Upload ID & URL
│
▼
2. PUT /parts/{n} ──> Upload 8 MiB Chunks (Parallel or Sequential) ──> Record receivedParts
│
▼
3. upload_complete ──> Verify Part Count ──> Convert reservedBytes to usedBytes ──> File Active
│
└── (If Aborted) ──> upload_cancel ──> Delete Chunks ──> Release reservedBytes
-
Upload Reservation (
upload_begin):- The client provides a unique
requestId(UUID),name,mediaType, andsize. - Siglata verifies that
size <= (limitBytes - usedBytes - reservedBytes). - The declared bytes are atomically added to
reservedBytes, and an upload session is created with an expiration timestamp. - The server returns an upload identifier and transfer instructions containing the HTTP
PUTURL template.
- The client provides a unique
-
Chunk Transmission (
PUT /v1/mcp/uploads/{uploadId}/parts/{partNumber}):- The client streams parts numbered sequentially from
1toN(up to 10,000). - Each part must be exactly
8,388,608bytes, except for the final part which sends the remaining declared bytes. - Parts can be uploaded concurrently. The server validates each chunk’s length and records its arrival in the upload manifest.
- The client streams parts numbered sequentially from
-
Sealing & Activation (
upload_complete):- Once all parts have been received, the client calls
upload_complete. - The server verifies that every part from
1toNis accounted for and that the total uploaded size matches the reservation. - The upload record transitions to
completed, the file is markedactive, and the reserved capacity moves fromreservedBytesintousedBytes. - This operation is completely idempotent: retrying
upload_completewith the same upload ID returns the active file record safely.
- Once all parts have been received, the client calls
-
Cancellation & Expiration (
upload_cancel):- If an upload is cancelled by the user or client,
upload_canceltriggers cleanup of any stored chunks and immediately releases thereservedBytes. - If a client disconnects unexpectedly, background reconciliation automatically detects expired upload sessions, releases their reservations, and cleans up orphaned chunks.
- If an upload is cancelled by the user or client,
30-Day Trash & Recovery Lifecycle
Siglata implements a safety-first data retention model to protect teams against accidental file deletion:
Moving to Trash (file_trash)
When a member deletes an active file:
- The file’s status transitions from
activetotrash. - The timestamp of deletion is recorded (
trashed_at = clock_timestamp()). - The recovery deadline is set to exactly 30 days in the future:
recover_until = clock_timestamp() + interval '30 days' - The file disappears from the default files list but appears in the trash view (
files_listwithstate: "trash"). - Quota Accounting: The file’s bytes remain charged to the organization’s
usedBytes. This guarantees that the stored data is fully preserved and that restoring the file will never fail due to quota exhaustion.
Restoring Files (file_restore)
If a file was deleted accidentally:
- Any member with
files:writepermissions can invokefile_restoreat any point beforerecover_until. - The file atomically returns to
activestatus, clearingtrashed_atandrecover_until. - Because the file’s bytes were already accounted for in
usedByteswhile in trash, restoration consumes zero additional storage quota.
Automated Permanent Purge
- Once the 30-day recovery deadline passes (
recover_until <= clock_timestamp()), restoration is permanently disabled. - The Siglata background reconciliation worker queries for expired trash items in batches:
SELECT id FROM files_object WHERE state = 'trash' AND recover_until <= clock_timestamp() - The worker marks the object for purging, deletes the stored binary blobs from the object storage tier, and cleans up the database record.
- Only after binary deletion is confirmed are the file’s bytes permanently subtracted from the organization’s
usedBytes, freeing up quota for new uploads.
Metadata Operations
File Renaming (file_rename)
Renaming a file in Siglata updates only the database record’s name attribute. Because stored object blobs are addressed by immutable UUIDs, renaming is an instantaneous, zero-cost operation that requires no byte copying or quota adjustment.
Streaming Downloads (file_download)
Authorized members can download files directly via the authenticated download endpoint (GET /v1/mcp/files/{fileId}/download). The endpoint validates the caller’s active organization membership in real time before streaming bytes, ensuring that revoked members or former collaborators cannot access organization assets.
Folders and Access Control
Files can live in nested folders, and every file and folder carries a visibility flag:
org(default): Every organization member can read and write the object, subject to their OAuth scopes. This preserves the behavior of all files created before folders existed.restricted: Only the object’s creator, organizationowner/adminmembers, and members holding an explicit grant can access the object.
A restricted folder restricts its entire subtree: to read or write an object, the caller must satisfy every restricted folder in its ancestor chain. A grant on a folder extends to its unrestricted descendants, while a restricted object inside a shared folder still requires its own grant.
- Denied operations fail with the
forbiddenerror code; restricted objects a member cannot read are also omitted fromfiles_listandfolders_listresults. grant_createandgrant_revokeare limited to the object’s creator and organization administrators. To change a grant level or grantee, revoke then create (there is no separate replace operation).file_set_visibilityandfolder_set_visibilityrequire write access on the object; they do not change existing grants.file_moveandupload_begininto a folder require write access on the destination folder.- Trashing a folder does not trash its contents; when a trashed folder passes its recovery deadline, it is deleted and its children move to the organization root.