40.1 Introduction
Chapters 34–39 established the database, authentication, authorization, and API foundations of the ACAI platform.
The next major subsystem is file storage.
An AI platform may process many types of user-owned data:
Images
Videos
Audio
Documents
PDF files
Text files
Generated media
Processed outputs
Temporary processing artifacts
These objects should not normally be stored directly inside PostgreSQL.
Instead, the system should separate:
Metadata
↓
PostgreSQL
Large binary objects
↓
Object Storage
The resulting architecture is:
ACAI
│
┌──────┴──────┐
│ │
PostgreSQL Object Storage
│ │
Metadata Binary Data
This separation improves scalability, performance, lifecycle management, and security.
40.2 Database vs Object Storage
PostgreSQL is appropriate for structured information such as:
fileId
ownerId
projectId
fileName
mimeType
size
status
storageKey
createdAt
Object storage is appropriate for:
photo.jpg
video.mp4
audio.wav
document.pdf
generated-image.png
The database therefore acts as the authoritative metadata layer.
40.3 File Architecture
The basic flow is:
User
│
▼
Upload Request
│
▼
Authentication
│
▼
Authorization
│
▼
Upload Policy
│
▼
Object Storage
│
▼
Metadata Record
│
▼
Processing Pipeline
For larger files, the architecture can use a direct or multipart upload pattern:
Client
│
▼
API requests upload authorization
│
▼
Server validates request
│
▼
Temporary upload authorization
│
▼
Object Storage
│
▼
Upload completed
│
▼
Server records/updates metadata
The client should not receive unrestricted storage credentials.
40.4 Storage Key Design
A file should have an internal storage key rather than relying only on its original filename.
For example:
users/{userId}/projects/{projectId}/files/{fileId}/original
A generated asset could use:
users/{userId}/projects/{projectId}/generations/{generationId}/output
The exact format can vary, but the key should be:
- unique;
- predictable only where necessary;
- independent from user-provided filenames;
- free of secrets;
- compatible with lifecycle policies.
40.5 Original Filename vs Storage Key
Suppose the user uploads:
my vacation photo.jpg
The application can store:
name = "my vacation photo.jpg"
while using an internal key such as:
users/<user>/projects/<project>/files/<file>/original
This separation prevents user-provided filenames from becoming the primary identity of the stored object.
40.6 File Metadata
The File model introduced earlier can be extended.
Example:
model File {
id String @id @default(uuid())
name String
mimeType String
sizeBytes BigInt
status FileStatus @default(UPLOADING)
ownerId String
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
projectId String?
project Project? @relation(fields: [projectId], references: [id], onDelete: SetNull)
storageKey String?
checksum String?
metadata Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([ownerId])
@@index([projectId])
@@index([status])
}
Potential metadata includes:
width
height
duration
pageCount
encoding
processing status
Only appropriate metadata should be stored.
40.7 File Lifecycle
A file should have an explicit lifecycle.
Example:
UPLOADING
↓
UPLOADED
↓
PROCESSING
↓
READY
Failure path:
PROCESSING
↓
FAILED
Deletion:
READY
↓
DELETED
This allows the application to distinguish between:
File exists but is still processing
and:
File processing failed
40.8 Upload Validation
A secure upload system should validate at least:
File size
Declared MIME type
Actual file characteristics
Extension
Application-supported format
Ownership
Destination project
Upload authorization
The client-provided filename or MIME type should not automatically be treated as authoritative.
40.9 File Size Limits
Different resources can have different limits.
For example:
Avatar
↓
Small limit
Document
↓
Medium limit
Video
↓
Large limit
The exact limits should be defined by product requirements, infrastructure capacity, and abuse testing.
The server must enforce the limit rather than relying only on the UI.
40.10 MIME Type Validation
A browser may report:
image/jpeg
but the server should not blindly trust that declaration.
Where appropriate, the processing pipeline should inspect the actual file characteristics.
The principle is:
Declared metadata
+
Content inspection
+
Application policy
40.11 Extension Handling
User-controlled filenames can contain unusual or misleading extensions.
The application should not construct sensitive storage behavior directly from an untrusted filename.
Instead:
Original filename
↓
Stored as metadata
↓
Internal object key
The storage key should be generated by the application.
40.12 File Ownership
Every private file must have an ownership boundary.
Conceptually:
File
│
├── ownerId
└── projectId
Before access:
Current User
↓
Project/File
↓
Ownership or Membership
↓
Permission
Only after successful authorization should the application provide access.
40.13 Private Storage
Private user files should generally remain inaccessible through anonymous public URLs.
The preferred pattern is:
User
│
▼
Authenticated API
│
▼
Authorization Check
│
▼
Temporary Authorized Access
│
▼
Object Storage
This prevents possession of an arbitrary URL from automatically becoming permanent access.
40.14 Temporary Access
For private objects, the application can issue a short-lived authorized access mechanism when supported by the storage platform.
Conceptually:
Request
↓
Authenticate
↓
Authorize
↓
Generate temporary access
↓
Client retrieves object
The lifetime should be limited to the actual use case.
40.15 Upload Workflow
A robust upload sequence is:
1. User selects file
↓
2. Client requests upload authorization
↓
3. Server authenticates user
↓
4. Server validates project ownership
↓
5. Server validates requested file policy
↓
6. Server creates file metadata
↓
7. Upload authorization issued
↓
8. Client uploads object
↓
9. Upload completion recorded
↓
10. Processing job created
This creates a traceable lifecycle.
40.16 Processing Pipeline
After upload:
Object Storage
↓
Processing Queue
↓
Worker
↓
Validation
↓
Extraction
↓
Transformation
↓
Result
↓
Database
For a document:
PDF
↓
Text Extraction
↓
Normalization
↓
Chunking
↓
Embedding
↓
Vector Index
For an image:
Image
↓
Metadata Extraction
↓
Validation
↓
Optional Processing
↓
Generated/Processed Asset
40.17 Malware and Unsafe File Handling
File-processing infrastructure should assume that uploaded files are untrusted.
The processing environment should therefore be isolated from privileged infrastructure as much as practical.
Important principles include:
Untrusted file
↓
Controlled processing environment
↓
Limited permissions
↓
No unnecessary network access
↓
Validated output
The exact security controls depend on deployment architecture.
40.18 Processing Isolation
A file-processing worker should not automatically have access to:
Production database credentials
Authentication secrets
Payment credentials
Administrative APIs
Unrelated user files
The worker should receive only the permissions required for its task.
This follows least-privilege design.
40.19 Temporary Files
Processing may require temporary local storage.
For example:
Object Storage
↓
Worker temporary area
↓
Processing
↓
Output Storage
↓
Temporary data removed
Temporary files should not remain indefinitely.
A lifecycle policy should define:
When created
Where stored
Who can access
When deleted
40.20 Generated Assets
AI-generated outputs should be treated as first-class resources.
For example:
Generation
│
├── request metadata
├── model information
├── status
└── output File
A generation may create:
image
video
audio
document
The output should have an identifiable relationship to the generation record.
40.21 File Versioning
Editing systems may need versions.
Example:
Original
↓
Edited Version 1
↓
Edited Version 2
↓
Edited Version 3
A future model can represent:
FileVersion
├── id
├── fileId
├── versionNumber
├── storageKey
├── createdAt
└── metadata
This is especially useful for creative applications where users expect undo, revision history, or recovery.
40.22 Derivative Assets
One source file may generate several derivatives.
For example:
Original Image
│
├── Thumbnail
├── Preview
├── Optimized Version
└── Edited Version
The database should maintain relationships between the source and derivative assets.
This prevents the application from losing track of how an output was created.
40.23 Content Hashing
A checksum can help detect accidental corruption or identify identical content.
Conceptually:
File
↓
Hash Function
↓
Checksum
The checksum can be stored in metadata.
However, a checksum should not automatically be treated as a security mechanism for every purpose.
Its exact role should be clearly defined.
40.24 Storage Quotas
Users may receive storage limits.
Conceptually:
User
│
├── Storage Used
├── Storage Limit
└── Available Storage
Before accepting an upload:
Requested Size
↓
Current Usage
↓
Quota Check
↓
Allowed / Rejected
Quota calculations should be performed server-side.
40.25 Project-Level Quotas
The same principle can apply to projects.
Account Quota
│
├── Project A
├── Project B
└── Project C
A project can optionally have its own resource limit.
This is useful for collaborative or organizational environments.
40.26 File Deletion
Deleting a file should be a controlled workflow.
Delete Request
↓
Authenticate
↓
Authorize
↓
Mark Deleted
↓
Storage Cleanup
↓
Audit Event
For important data, a delayed deletion strategy may be preferable:
ACTIVE
↓
MARKED_FOR_DELETION
↓
RETENTION PERIOD
↓
PERMANENT DELETION
This can provide a recovery window.
40.27 Orphaned Object Detection
Distributed systems can create inconsistencies.
For example:
Database record exists
but object is missing
or:
Object exists
but database record was never completed
A reconciliation process can periodically detect such cases.
Conceptually:
Database
↕
Object Storage
↕
Reconciliation Worker
Possible states:
CONSISTENT
MISSING_OBJECT
MISSING_METADATA
PROCESSING_STUCK
40.28 Storage Lifecycle Policies
Object storage should use lifecycle rules where appropriate.
Example:
Temporary upload
↓
Delete after defined retention period
Processing artifacts:
Temporary artifact
↓
Automatic cleanup
Old versions:
Inactive version
↓
Long-term retention or deletion
Lifecycle policies reduce unnecessary storage cost and operational clutter.
40.29 Download Authorization
A download endpoint should not simply accept:
/api/files/{id}/download
and return the object.
Instead:
Request
↓
Authenticate
↓
Find File
↓
Check Owner/Member
↓
Check Permission
↓
Generate Authorized Access
↓
Return
This preserves the same authorization principles established in Chapter 38.
40.30 File API
A possible API structure is:
POST /api/files
GET /api/files
GET /api/files/{id}
DELETE /api/files/{id}
POST /api/files/{id}/complete
POST /api/files/{id}/process
For larger uploads, the API may separate:
Create upload
Complete upload
Process file
from ordinary file metadata operations.
40.31 File API Security
Every private file endpoint should consider:
Authentication
Authorization
Ownership
Project membership
File status
Quota
Rate limits
Input validation
Audit logging
This is especially important because file identifiers are often easy for clients to obtain.
Knowing an identifier must not automatically grant access.
40.32 AI Document Pipeline
The file architecture becomes especially important when documents are used by AI.
The secure pipeline is:
Upload
↓
Validation
↓
Storage
↓
Text Extraction
↓
Chunking
↓
Embedding
↓
Vector Storage
↓
Authorized Retrieval
↓
AI Context
The retrieval stage must respect the same ownership and permission boundaries as the original document.
40.33 Document Authorization
Suppose:
Project A
├── Document 1
└── Document 2
Project B
└── Document 3
If a user has access only to Project A, an AI retrieval query must not accidentally return Document 3.
Therefore:
Semantic relevance
+
Authorization filter
must both be satisfied.
A relevant document is not automatically an authorized document.
40.34 Retrieval Security Principle
The correct model is:
Candidate Documents
↓
Authorization Filter
↓
Eligible Documents
↓
Relevance Ranking
↓
AI Context
An alternative implementation can apply authorization before ranking.
The critical requirement is that unauthorized documents never reach the model context.
40.35 Media Processing
For video and audio, processing can be asynchronous.
Example:
Upload
↓
Metadata Extraction
↓
Queue
↓
Worker
↓
Transcoding / Analysis
↓
Output
The user interface can display:
Uploading
Processing
Completed
Failed
rather than waiting for a long HTTP request.
40.36 Progress Tracking
A processing job can expose:
0%
25%
50%
75%
100%
However, progress should be reported only when it can be estimated meaningfully.
A system should not create false precision simply to make the interface look active.
40.37 Storage and Privacy
Files can contain highly sensitive information.
Therefore:
Access Control
Encryption
Retention
Deletion
Audit
should be considered together.
The system should collect and retain only what is required for its intended functionality.
40.38 Backup Strategy
Object storage and database backups should be considered separately.
PostgreSQL Backup
+
Object Storage Protection
A database backup without the corresponding objects may be incomplete.
Likewise, objects without their metadata may be difficult to interpret.
A recovery plan should define how both layers are restored together.
40.39 Disaster Recovery
A recovery workflow may look like:
Incident
↓
Infrastructure Recovery
↓
Database Restore
↓
Object Storage Verification
↓
Metadata/Object Reconciliation
↓
Application Recovery
↓
Integrity Verification
Recovery procedures should be tested rather than merely documented.
40.40 File Security Checklist
[ ] Files are treated as untrusted input
[ ] File ownership is recorded
[ ] Project access is verified
[ ] Size limits are enforced
[ ] File types are validated
[ ] User filenames are not used as object identity
[ ] Storage keys are generated server-side
[ ] Private objects are not anonymously accessible
[ ] Temporary access is time-limited
[ ] Processing workers use least privilege
[ ] Temporary files are cleaned up
[ ] Deleted objects follow a defined lifecycle
[ ] Orphaned objects can be detected
[ ] Storage quotas are enforced
[ ] File operations are auditable
[ ] Database and storage recovery are coordinated
40.41 Complete Storage Architecture
The complete design is:
USER
│
▼
AUTHENTICATION
│
▼
AUTHORIZATION
│
▼
FILE API
│
┌───────────┴───────────┐
│ │
File Metadata Upload Policy
│ │
▼ ▼
PostgreSQL Object Storage
│ │
└───────────┬───────────┘
│
▼
Processing Queue
│
▼
Worker
│
┌────────────┴────────────┐
│ │
Document Pipeline Media Pipeline
│ │
▼ ▼
Text/Embeddings Derived Assets
│ │
└────────────┬────────────┘
│
▼
AI Services
40.42 Architectural Significance
The file system is not merely a place to put uploaded files.
It is a security and data-governance subsystem.
It determines:
Who owns the data?
Who can access it?
How long is it retained?
How is it processed?
Where is it stored?
What happens when it is deleted?
Can it be recovered?
Can AI systems retrieve it?
Can processing workers access it?
These questions must be answered before the platform handles significant user data.
40.43 Conclusion
Chapter 40 established the object-storage architecture.
The system now has a clear separation:
Structured metadata
↓
PostgreSQL
Large binary objects
↓
Object Storage
and a controlled lifecycle:
Upload
↓
Validate
↓
Store
↓
Process
↓
Authorize
↓
Retrieve
↓
Retain
↓
Delete
The most important principle is:
A file being stored does not automatically make it accessible.
Every access must pass through identity, authorization, ownership, and policy boundaries.
This architecture prepares the platform for the next major subsystem: document ingestion, text extraction, chunking, embeddings, vector storage, retrieval, and Retrieval-Augmented Generation (RAG).
END OF CHAPTER 40
Top comments (0)