🏛️ Repository Vault Feature Design
Target Branch:
repositoryOwner: Platform
Last Updated: 2025-10-02
📚 Table of Contents
- 1. Problem / Motivation
- 2. Goals / Non-Goals
- 3. User Stories (MVP)
- 4. Data Model (Initial)
- 5. Storage & Encryption Strategy
- 6. API Endpoints (MVP)
- 7. Expiration & Notification Logic
- 8. Versioning Semantics
- 9. UI Plan
- 10. Security Considerations
- 11. Migration Plan
- 12. Risks / Mitigations
- 13. Open Questions
- 14. Minimal Initial Migration (Sketch)
- 15. Success Criteria (MVP)
1. Problem / Motivation
A secure, centralized place to store and manage critical personal or project-related documents (licenses, certificates, warranties, IDs). Today, documents may be attached to workflows, but there is no canonical, user-centric repository with:
- Private-by-default visibility
- Version history (retain superseded copies)
- Expiration tracking / renewal reminders
- Archival vs destruction lifecycle
- Controlled sharing (user-specific or token-based temporary access)
2. Goals / Non-Goals
Goals:
- Fast MVP: upload, list, view metadata, download latest version
- Private by default; explicit sharing only
- Track expiry + notify upcoming (e.g., 30/7/1 days)
- Immutable versions (append-only) with file hash
- Soft archive & soft delete initial (hard delete admin script)
- Minimal ergonomic UI (table/grid + detail pane)
Deferred / Non-Goals (Phase 2+):
- Full-text OCR search
- External storage backends abstraction (start with same storage/S3-like)
- Complex tagging taxonomy (start with free-form tag array)
- End-to-end encryption in browser (consider later)
3. User Stories (MVP)
- Upload a document (PDF/JPG/PNG) and set name + category + optional expiry date.
- See a list of my documents sorted by updated date with filters (expired, expiring soon, archived).
- Receive a notification before a document expires.
- Upload a new version while retaining older versions for audit.
- Archive a document to hide it from active lists.
- Permanently destroy (soft delete first) an archived document I own.
- Share a document with another authenticated user (Phase 1) or generate a revocable token link (Phase 2).
4. Data Model (Initial)
Tables (Drizzle / Postgres):
// RepositoryDocuments
id: uuid (pk)
ownerUserId: varchar(128) not null
name: varchar(160) not null
category: varchar(64) null
tags: text[] default '{}'
status: varchar (enum: 'active' | 'archived' | 'destroyed') default 'active'
visibility: varchar (enum: 'private' | 'shared') default 'private'
latestVersionId: uuid null (FK -> versions.id, maintained by trigger or app logic)
expiresAt: timestamp null
archivedAt: timestamp null
destroyedAt: timestamp null
createdAt: timestamp default now()
updatedAt: timestamp default now()
// RepositoryDocumentVersions
id: uuid pk
documentId: uuid fk -> documents.id on delete cascade
version: int not null (1..n unique per document)
fileName: varchar(255) not null
mimeType: varchar(128) null
size: int null
hashSha256: char(64) not null
storageKey: text not null (path / object key)
createdAt: timestamp default now()
uploadedByUserId: varchar(128) not null
supersedesVersionId: uuid null fk self (optional)
// RepositoryDocumentShares (Phase 1 user-based)
id: uuid pk
documentId: uuid fk -> documents.id
sharedWithUserId: varchar(128) not null
createdByUserId: varchar(128) not null
createdAt: timestamp default now()
revokedAt: timestamp null
// RepositoryDocumentAuditEvents
id uuid pk
documentId uuid fk
versionId uuid null fk
action varchar(64) not null // upload_version, archive, restore, destroy, share, revoke, download
actorUserId varchar(128) not null
meta json null
createdAt timestamp default now()
Indexes:
- documents(ownerUserId, status)
- versions(documentId, version desc)
- shares(documentId, sharedWithUserId unique active)
- audit(documentId, createdAt desc)
- expiry partial index where expiresAt is not null and status='active'
5. Storage & Encryption Strategy
Hybrid approach:
- Per-version envelope encryption (AES-256-GCM) with random DEK.
- DEK wrapped by master key / KMS (placeholder local master key for MVP) stored as
encryptedDEK. - Small ciphertext (<= INLINE_FILE_MAX_BYTES) stored inline (bytea). Larger stored via object storage using abstraction.
lib/storage/repository.tsabstraction:
- putObject({ storageKey, stream|buffer, mimeType })
- getObjectStream(storageKey)
- deleteObject(storageKey) (GC / hard delete only)
storageKey pattern: repo/<ownerUserId>/<documentId>/<versionNumber>/<sanitizedFileName>
Security additions:
- Hash (sha256) persisted for integrity
- authTag + iv stored per version
- Future: client-side zero-knowledge option (Phase 2)
6. API Endpoints (MVP)
POST /api/repository/upload -> create document (if first) + version
GET /api/repository -> list (filters: status, expiringSoon, search=name substring)
GET /api/repository/:id -> metadata + latest version pointer
GET /api/repository/:id/versions -> list versions
GET /api/repository/:id/download -> decrypt & stream latest (signed URL optional if object storage pre-signed)
GET /api/repository/:id/versions/:v/download -> decrypt & stream specific version
GET /api/repository/:id/view -> inline (Content-Disposition inline) for viewer (pdf/image)
PATCH /api/repository/:id -> update name/category/tags/expiry
POST /api/repository/:id/archive -> archive
POST /api/repository/:id/restore -> restore
POST /api/repository/:id/destroy -> soft destroy (status=destroyed + destroyedAt)
POST /api/repository/:id/version -> add new version
POST /api/repository/:id/share -> add share (Phase 1)
POST /api/repository/:id/unshare -> revoke user share
Authorization rules enforced server-side per owner or share membership.
7. Expiration & Notification Logic
A scheduled job (future cron or on-demand server action) queries:
SELECT id, ownerUserId, expiresAt
FROM RepositoryDocuments
WHERE status='active'
AND expiresAt BETWEEN now() AND now() + interval '30 days'
Bucket by days remaining (30/7/1) -> insert Notifications rows with type repo.expiry.upcoming.
8. Versioning Semantics
- Increment version sequentially (SELECT max(version)+1)
- Maintain
latestVersionIdon document (transaction) for quick lookups - Allow downloading any historical version
- Optional: if same hash uploaded consecutively, skip new version (Phase 2 optimization)
9. UI Plan
Route Group: app/(repository) isolates vault layout.
Pages:
/repository(list): table (Name / Category / Status / Expires / Updated / Actions) with personal/public scopes/repository/new(document-type guided upload with attachment slots)/repository/[documentId]detail: metadata, version list, inline viewer, transcription queue, audit hooks- Components:
RepositoryUploadFormRepositoryDocumentRowRepositoryDocumentViewer(inline iframe + attachment cards)RepositoryMetadataEditorVersionHistoryTableExpiryBadgeShareDialog(Phase 1 user search)
Implementation status (Oct 2025):
- Route group + vault list with scope tabs, search, inline preview launcher
- Guided upload form with document-type templates (driver's license, warranty, generic)
- Detail page with inline viewer, metadata editor, transcription queue surfacing
- Sharing dialog + endpoints
- Expiry badge automation & notifications wiring
10. Security Considerations
- All documents private unless shared explicitly
- No public unauthenticated links in MVP
- Signed URLs TTL (e.g., 2 minutes)
- Enforce ownership on mutation
- Ensure soft-deleted & archived docs excluded appropriately
- Sanitize filenames (strip path chars, limit length)
- Hash verification to detect tampering (server recomputes SHA-256 of uploaded buffer/stream)
11. Migration Plan
- Create feature branch
feat/repository-vault - Add schema + migrations (000X_repository_core.sql)
- Implement storage abstraction
- Implement API handlers incrementally:
- upload (create + version)
- list / get / download
- version add
- metadata patch
- archive / restore / destroy
- Add UI route group & list page
- Add detail + version history
- Add notifications for expiry (stub job / manual script)
- Write README updates & usage docs
- QA & merge -> main
12. Risks / Mitigations
| Risk | Mitigation |
|---|---|
| Large file uploads blocking server | Stream to storage provider; enforce size limit (config) |
| Race on concurrent version uploads | DB transaction w/ max(version) FOR UPDATE |
| Orphaned storage objects on soft delete | Keep; periodic GC script for destroyed > retention window |
| Notification spam | De-dup by (doc, dayBucket) key |
13. Open Questions
- Need encryption at rest beyond provider? (Defer)
- Tag search implementation (GIN on text[] vs JSONB?) – initial simple = text[] + @> / ILIKE name filter.
- Should shares allow role-based groups later? (Future extension: RepositoryShareGroups)
14. Minimal Initial Migration (Sketch)
CREATE TABLE "RepositoryDocuments" (...);
CREATE INDEX idx_repo_docs_owner ON "RepositoryDocuments" ("ownerUserId", "status");
CREATE TABLE "RepositoryDocumentVersions" (...);
CREATE UNIQUE INDEX uq_repo_version_per_doc ON "RepositoryDocumentVersions" ("documentId", "version");
ALTER TABLE "RepositoryDocuments" ADD CONSTRAINT repo_latest_version_fk
FOREIGN KEY ("latestVersionId") REFERENCES "RepositoryDocumentVersions"("id") ON DELETE SET NULL;
(See final migration file in branch for exact SQL with constraints & timestamps.)
15. Success Criteria (MVP)
Encryption Specific Success:
- Version ciphertext stored (no plaintext at rest)
- Decryption path validated (round trip test)
- Size limit enforcement per MIME type
End of draft.
Project Roadmap & Progress
For a detailed, actionable project timeline and ongoing development updates, see:
These files are updated as the project advances and provide visibility into phases, milestones, and daily progress.