FEATURE: Customer Backup System
Overview
Enable CODITECT customers to backup and restore their context database and Claude config files through the CODITECT Cloud API, without requiring direct GCS access or gcloud authentication.
Business Value
- Data Protection: Customers can recover from data loss
- Multi-Device Sync: Restore context on new machines
- Enterprise Compliance: Audit trails, retention policies
- Upsell Opportunity: Tiered backup retention by plan
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ CUSTOMER MACHINE │
│ │
│ ┌──────────────┐ ┌──────────────────────────────────┐ │
│ │ context.db │ │ backup-context-db.sh --cloud │ │
│ │ Claude config│───▶│ │ │
│ └──────────────┘ │ 1. Compress files │ │
│ │ 2. Read license key │ │
│ │ 3. Upload via API │ │
│ └──────────────────────────────────┘ │
└────────────────────────────────┬────────────────────────────────┘
│ HTTPS + API Key
▼
┌─────────────────────────────────────────────────────────────────┐
│ api.coditect.ai │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ BackupViewSet │ │
│ │ POST /api/v1/backup/upload - Upload backup │ │
│ │ GET /api/v1/backup/list - List backups │ │
│ │ GET /api/v1/backup/{id} - Get backup details │ │
│ │ GET /api/v1/backup/{id}/download - Download backup │ │
│ │ DELETE /api/v1/backup/{id} - Delete backup │ │
│ └────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────┴───────────────────────────────┐ │
│ │ BackupService │ │
│ │ - Tenant isolation (auto via django-multitenant) │ │
│ │ - Retention policy enforcement │ │
│ │ - Storage quota management │ │
│ │ - Encryption at rest │ │
│ └────────────────────────────────────────────────────────┘ │
└────────────────────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ GCS: gs://coditect-customer-backups/ │
│ │
│ /{tenant_id}/{user_id}/{timestamp}/ │
│ ├── context.db.gz.enc │
│ ├── unified_messages.jsonl.gz.enc │
│ ├── unified_hashes.json.gz.enc │
│ ├── unified_stats.json.gz.enc │
│ ├── claude-config/ │
│ │ ├── settings.json.gz.enc │
│ │ ├── settings.local.json.gz.enc │
│ │ └── statusline-config.json.gz.enc │
│ └── manifest.json │
└─────────────────────────────────────────────────────────────────┘
Pricing Tiers
| Plan | Retention | Max Backups | Max Size | Auto-Backup |
|---|---|---|---|---|
| Free | 7 days | 3 | 1 GB | No |
| Pro | 30 days | 10 | 10 GB | Daily |
| Team | 90 days | 30 | 50 GB | Daily |
| Enterprise | 1 year | Unlimited | Unlimited | Configurable |
Task Breakdown
Track A: Backend API
A.11.1 Create Backup Models
Status: [ ] Pending
Effort: 2 hours
Files:
backend/coditect_license/backup/models.py
class Backup(TenantModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
user = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
size_bytes = models.BigIntegerField()
storage_path = models.CharField(max_length=500)
manifest = models.JSONField() # File list, checksums
status = models.CharField(choices=BACKUP_STATUS, default='pending')
retention_until = models.DateTimeField()
class Meta:
indexes = [
models.Index(fields=['user', '-created_at']),
]
A.11.2 Create Backup Serializers
Status: [ ] Pending
Effort: 1 hour
Files:
backend/coditect_license/backup/serializers.py
class BackupSerializer(serializers.ModelSerializer):
download_url = serializers.SerializerMethodField()
class Meta:
model = Backup
fields = ['id', 'created_at', 'size_bytes', 'manifest',
'status', 'retention_until', 'download_url']
A.11.3 Create BackupService
Status: [ ] Pending
Effort: 4 hours
Files:
backend/coditect_license/backup/services.py
class BackupService:
def __init__(self, tenant, user):
self.tenant = tenant
self.user = user
self.storage = GCSBackupStorage()
def create_backup(self, files: List[UploadedFile]) -> Backup:
"""Upload files to GCS, create Backup record."""
pass
def list_backups(self) -> QuerySet[Backup]:
"""List user's backups (tenant-isolated)."""
pass
def get_download_url(self, backup_id: UUID) -> str:
"""Generate signed URL for download."""
pass
def delete_backup(self, backup_id: UUID) -> None:
"""Delete backup from GCS and database."""
pass
def enforce_retention(self) -> int:
"""Delete expired backups, return count."""
pass
def check_quota(self, size_bytes: int) -> bool:
"""Check if user has quota for new backup."""
pass
A.11.4 Create BackupViewSet
Status: [ ] Pending
Effort: 3 hours
Files:
backend/coditect_license/backup/views.py
class BackupViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated, HasValidLicense]
serializer_class = BackupSerializer
@action(detail=False, methods=['post'])
def upload(self, request):
"""Upload new backup (multipart form)."""
pass
@action(detail=True, methods=['get'])
def download(self, request, pk=None):
"""Get signed download URL."""
pass
A.11.5 Create GCS Storage Backend
Status: [ ] Pending
Effort: 3 hours
Files:
backend/coditect_license/backup/storage.py
class GCSBackupStorage:
BUCKET = 'coditect-customer-backups'
def upload(self, tenant_id, user_id, files) -> str:
"""Upload files, return storage path."""
pass
def download_url(self, path: str, expiry: int = 3600) -> str:
"""Generate signed download URL."""
pass
def delete(self, path: str) -> None:
"""Delete backup from GCS."""
pass
A.11.6 Add URL Routes
Status: [ ] Pending
Effort: 30 min
Files:
backend/coditect_license/backup/urls.pybackend/coditect_license/urls.py
router.register(r'backup', BackupViewSet, basename='backup')
A.11.7 Create Retention Celery Task
Status: [ ] Pending
Effort: 2 hours
Files:
backend/coditect_license/backup/tasks.py
@shared_task
def enforce_backup_retention():
"""Run daily to delete expired backups."""
for tenant in Tenant.objects.all():
set_current_tenant(tenant)
service = BackupService(tenant, None)
deleted = service.enforce_retention()
logger.info(f"Tenant {tenant.id}: deleted {deleted} expired backups")
Track C: Infrastructure
C.6.1 Create Customer Backup GCS Bucket
Status: [ ] Pending
Effort: 1 hour
Commands:
gsutil mb -l us-central1 -c STANDARD gs://coditect-customer-backups
gsutil iam ch serviceAccount:backend@coditect-citus-prod.iam.gserviceaccount.com:objectAdmin gs://coditect-customer-backups
C.6.2 Configure Bucket Lifecycle
Status: [ ] Pending
Effort: 30 min
Files:
infra/gcs-lifecycle-customer-backups.json
{
"lifecycle": {
"rule": [
{
"action": {"type": "Delete"},
"condition": {"age": 365, "matchesPrefix": ["free/"]}
}
]
}
}
C.6.3 Add Backend Service Account Permissions
Status: [ ] Pending
Effort: 30 min
C.6.4 Update Terraform/Infrastructure as Code
Status: [ ] Pending
Effort: 2 hours
Files:
infra/terraform/gcs.tf
Track A: Client Script Updates
A.11.8 Add --cloud Flag to backup-context-db.sh
Status: [ ] Pending
Effort: 3 hours
Files:
scripts/backup-context-db.sh
# New cloud backup mode
cloud_backup() {
local license_file="$HOME/.coditect/licensing/license.json"
# Check license
if [[ ! -f "$license_file" ]]; then
log_error "No license found. Run: /license-activate <KEY>"
exit 1
fi
# Extract API key
local api_key=$(jq -r '.license_key' "$license_file")
local api_url="https://api.coditect.ai/api/v1/backup/upload"
# Prepare files (compress)
prepare_backup_files
# Upload via API
curl -X POST "$api_url" \
-H "Authorization: Bearer $api_key" \
-F "context_db=@$TEMP_DIR/context.db.gz" \
-F "unified_messages=@$TEMP_DIR/unified_messages.jsonl.gz" \
-F "claude_config=@$TEMP_DIR/claude-config.tar.gz"
}
A.11.9 Add --cloud-list and --cloud-restore
Status: [ ] Pending
Effort: 2 hours
Files:
scripts/backup-context-db.sh
cloud_list() {
curl -s "https://api.coditect.ai/api/v1/backup/" \
-H "Authorization: Bearer $api_key" | jq '.results'
}
cloud_restore() {
local backup_id="$1"
local download_url=$(curl -s "https://api.coditect.ai/api/v1/backup/$backup_id/download" \
-H "Authorization: Bearer $api_key" | jq -r '.url')
# Download and extract
curl -s "$download_url" | tar -xz -C "$CONTEXT_DIR"
}
A.11.10 Auto-Detect Mode (Local vs Cloud)
Status: [ ] Pending
Effort: 1 hour
# Auto-detect: use cloud if licensed, local if gcloud available
detect_backup_mode() {
if [[ -f "$LICENSE_FILE" ]]; then
echo "cloud"
elif command -v gcloud &> /dev/null; then
echo "local"
else
log_error "No backup method available"
exit 1
fi
}
Track E: Testing
E.6.1 Unit Tests for BackupService
Status: [ ] Pending
Effort: 3 hours
Files:
backend/coditect_license/backup/tests/test_services.py
E.6.2 API Integration Tests
Status: [ ] Pending
Effort: 2 hours
Files:
backend/coditect_license/backup/tests/test_views.py
E.6.3 Script Integration Tests
Status: [ ] Pending
Effort: 2 hours
Files:
tests/integration/test_backup_cloud.py
E.6.4 Tenant Isolation Tests
Status: [ ] Pending
Effort: 2 hours
Track F: Documentation
F.5.1 Customer Backup Guide
Status: [ ] Pending
Effort: 2 hours
Files:
docs/guides/BACKUP-GUIDE.md
F.5.2 API Documentation
Status: [ ] Pending
Effort: 1 hour
Files:
docs/api/backup-api.md
F.5.3 Update /backup Command
Status: [ ] Pending
Effort: 30 min
Files:
commands/backup.md
Dependencies
Milestones
| Milestone | Tasks | Target |
|---|---|---|
| M1: Backend API | A.11.1-A.11.7 | Week 1 |
| M2: Infrastructure | C.6.1-C.6.4 | Week 1 |
| M3: Client Integration | A.11.8-A.11.10 | Week 2 |
| M4: Testing | E.6.1-E.6.4 | Week 2 |
| M5: Documentation | F.5.1-F.5.3 | Week 2 |
| M6: Launch | Deploy to prod | Week 3 |
Security Considerations
- Encryption at Rest: All backups encrypted with tenant-specific keys
- Signed URLs: Download URLs expire after 1 hour
- Tenant Isolation: django-multitenant ensures data separation
- Rate Limiting: Max 10 backups per day per user
- Size Limits: Enforced per pricing tier
Rollback Plan
If issues arise:
- Disable
/backup --cloudflag - Customers fall back to local GCS backup (if configured)
- Existing backups remain accessible via API
Created: 2026-01-18 Author: Claude Opus 4.5 + Hal Casteel ADR: ADR-184 (customer-backup-architecture)