Move plans from doc/plan/ into doc/plans/ and add YYYY-MM-DD date prefixes to all undated plan files based on document headers or earliest git commit dates. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
8.5 KiB
8.5 KiB
Storage System Implementation Plan (V1)
Status: Draft
Owner: Backend + UI
Date: 2026-02-20
Goal
Add a single storage subsystem for Paperclip that supports:
- local disk storage for single-user local deployment
- S3-compatible object storage for cloud deployment
- a provider-agnostic interface for issue images and future file attachments
V1 Scope
- First consumer: issue attachments/images.
- Storage adapters:
local_diskands3. - Files are always company-scoped and access-controlled.
- API serves attachment bytes through authenticated Paperclip endpoints.
Out of Scope (This Draft)
- Public unauthenticated object URLs.
- CDN/signed URL optimization.
- Image transformations/thumbnails.
- Malware scanning pipeline.
Key Decisions
- Default local path is under instance root:
~/.paperclip/instances/<instanceId>/data/storage. - Object bytes live in storage provider; metadata lives in Postgres.
assetsis generic metadata table;issue_attachmentslinks assets to issues/comments.- S3 credentials come from runtime environment/default AWS provider chain, not DB rows.
- All object keys include company prefix to preserve hard tenancy boundaries.
Phase 1: Shared Config + Provider Contract
Checklist (Per File)
packages/shared/src/constants.ts: addSTORAGE_PROVIDERSandStorageProvidertype.packages/shared/src/config-schema.ts: addstorageConfigSchemawith:- provider:
local_disk | s3 - localDisk.baseDir
- s3.bucket, s3.region, s3.endpoint?, s3.prefix?, s3.forcePathStyle?
- provider:
packages/shared/src/index.ts: export new storage config/types.cli/src/config/schema.ts: ensure re-export includes new storage schema/types.cli/src/commands/configure.ts: addstoragesection support.cli/src/commands/onboard.ts: initialize default storage config.cli/src/prompts/storage.ts: new prompt flow for local disk vs s3 settings.cli/src/prompts/index(if present) or direct imports: wire new storage prompt.server/src/config.ts: load storage config and resolve home-aware local path.server/src/home-paths.ts: addresolveDefaultStorageDir().doc/CLI.md: documentconfigure --section storage.doc/DEVELOPING.md: document default local storage path and overrides.
Acceptance Criteria
paperclipai onboardwrites a validstorageconfig block by default.paperclipai configure --section storagecan switch between local and s3 modes.- Server startup reads storage config without env-only hacks.
Phase 2: Server Storage Subsystem + Providers
Checklist (Per File)
server/src/storage/types.ts: define provider + service interfaces.server/src/storage/service.ts: provider-agnostic service (key generation, validation, stream APIs).server/src/storage/local-disk-provider.ts: implement local disk provider with safe path resolution.server/src/storage/s3-provider.ts: implement S3-compatible provider (@aws-sdk/client-s3).server/src/storage/provider-registry.ts: provider lookup by configured id.server/src/storage/index.ts: export storage factory helpers.server/src/services/index.ts: exportstorageServicefactory.server/src/app.tsor route wiring point: inject/use storage service where needed.server/package.json: add AWS SDK dependency if not present.
Acceptance Criteria
- In
local_diskmode, uploading + reading a file round-trips bytes on disk. - In
s3mode, service canput/get/deleteagainst S3-compatible endpoint. - Invalid provider config yields clear startup/config errors.
Phase 3: Database Metadata Model
Checklist (Per File)
packages/db/src/schema/assets.ts: new generic asset metadata table.packages/db/src/schema/issue_attachments.ts: issue-to-asset linking table.packages/db/src/schema/index.ts: export new tables.packages/db/src/migrations/*: generate migration for both tables and indexes.packages/shared/src/types/issue.ts(or new asset types file): addIssueAttachmenttype.packages/shared/src/index.ts: export new types.
Suggested Columns
assets:id,company_id,provider,object_keycontent_type,byte_size,sha256,original_filenamecreated_by_agent_id,created_by_user_id, timestamps
issue_attachments:id,company_id,issue_id,asset_id,issue_comment_id(nullable), timestamps
Acceptance Criteria
- Migration applies cleanly on empty and existing local dev DB.
- Metadata rows are company-scoped and indexed for issue lookup.
Phase 4: Issue Attachment API
Checklist (Per File)
packages/shared/src/validators/issue.ts: add schemas for upload/list/delete attachment operations.server/src/services/issues.ts: add attachment CRUD helpers with company checks.server/src/routes/issues.ts: add endpoints:POST /companies/:companyId/issues/:issueId/attachments(multipart)GET /issues/:issueId/attachmentsGET /attachments/:attachmentId/contentDELETE /attachments/:attachmentId
server/src/routes/authz.ts: reuse/enforce company access for attachment endpoints.server/src/services/activity-log.tsusage callsites: log attachment add/remove mutations.server/src/app.ts: ensure multipart parsing middleware is in place for upload route.
API Behavior
- Enforce max size and image/content-type allowlist in V1.
- Return consistent errors:
400/401/403/404/409/422/500. - Stream bytes instead of buffering large payloads in memory.
Acceptance Criteria
- Board and same-company agents can upload and read attachments per issue permissions.
- Cross-company access is denied even with valid attachment id.
- Activity log records attachment add/remove actions.
Phase 5: UI Issue Attachment Integration
Checklist (Per File)
ui/src/api/issues.ts: add attachment API client methods.ui/src/api/client.ts: support multipart upload helper (no JSONContent-TypeforFormData).ui/src/lib/queryKeys.ts: add issue attachment query keys.ui/src/pages/IssueDetail.tsx: add upload UI + attachment list/query invalidation.ui/src/components/CommentThread.tsx: optional comment image attach or display linked images.packages/shared/src/types/index.ts: ensure attachment types are consumed cleanly in UI.
Acceptance Criteria
- User can upload an image from issue detail and see it listed immediately.
- Uploaded image can be opened/rendered via authenticated API route.
- Upload and fetch failures are visible to users (no silent errors).
Phase 6: CLI Doctor + Operational Hardening
Checklist (Per File)
cli/src/checks/storage-check.ts: add storage check (local writable dir, optional S3 reachability check).cli/src/checks/index.ts: export new storage check.cli/src/commands/doctor.ts: include storage check in doctor sequence.doc/DATABASE.mdordoc/DEVELOPING.md: mention storage backend behavior by deployment mode.doc/SPEC-implementation.md: add storage subsystem and issue-attachment endpoint contract.
Acceptance Criteria
paperclipai doctorreports actionable storage status.- Local single-user install works without extra cloud credentials.
- Cloud config supports S3-compatible endpoint without code changes.
Test Plan
Server Integration Tests
server/src/__tests__/issue-attachments.auth.test.ts: company boundary and permission tests.server/src/__tests__/issue-attachments.lifecycle.test.ts: upload/list/read/delete flow.server/src/__tests__/storage-local-provider.test.ts: local provider path safety and round-trip.server/src/__tests__/storage-s3-provider.test.ts: s3 provider contract (mocked client).server/src/__tests__/activity-log.attachments.test.ts: mutation logging assertions.
CLI Tests
cli/src/__tests__/configure-storage.test.ts: configure section writes valid config.cli/src/__tests__/doctor-storage-check.test.ts: storage health output and repair behavior.
UI Tests (if present in current stack)
ui/src/...: issue detail upload and error handling tests.
Verification Gate Before Merge
Run:
pnpm -r typecheck
pnpm test:run
pnpm build
If any command is skipped, document exactly what was skipped and why.
Implementation Order
- Phase 1 and Phase 2 (foundation, no user-visible breakage)
- Phase 3 (DB contract)
- Phase 4 (API)
- Phase 5 (UI consumer)
- Phase 6 (doctor/docs hardening)