ADR-158: Cloud Project Registration API
Status
ACCEPTED (2026-02-05)
Context
Problem Statement
CODITECT's local project registration (projects.db) operates independently from the cloud multi-tenant platform. This creates several challenges:
| Problem | Impact |
|---|---|
| No cloud visibility | Platform has no awareness of local projects |
| Cross-device sync impossible | Same project on different machines appears as separate projects |
| Team collaboration blocked | Cannot share project context with team members |
| Analytics gap | No aggregated project metrics across organization |
| Orphaned data | Cloud task tracking without project context |
Current State
Local (coditect-core):
projects.dbwithprojectstable (ADR-118 Tier 3)- Local UUID generation per machine
- No cloud awareness
Cloud (coditect-cloud-infra):
- Multi-tenant Django with
django-multitenant Projectmodel without local linking- Task tracking without project association
Requirements
- Offline-First: Must work without network connectivity
- Cross-Device: Same project identifiable across machines
- Multi-Tenant: Tenant isolation via
django-multitenant - Idempotent: Re-registration safe and deterministic
- Audit Trail: Track registration history
Decision
Cloud Project Registration API
Implement a REST API for bidirectional project synchronization.
API Endpoints
openapi: 3.0.3
info:
title: CODITECT Cloud Project Registration API
version: 1.0.0
paths:
/api/v1/projects/register/:
post:
summary: Register local project with cloud
requestBody:
content:
application/json:
schema:
type: object
required:
- local_project_uuid
- project_name
- machine_uuid
properties:
local_project_uuid:
type: string
format: uuid
project_name:
type: string
machine_uuid:
type: string
format: uuid
root_path:
type: string
primary_language:
type: string
framework:
type: string
content_hash:
type: string
project_type:
type: string
enum: [standalone, submodule, monorepo]
parent_project_uuid:
type: string
format: uuid
responses:
'201':
description: Project registered
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectRegistration'
'200':
description: Already registered (idempotent)
/api/v1/projects/{cloud_uuid}/registration-status/:
get:
summary: Check project registration status
responses:
'200':
description: Registration status
/api/v1/projects/sync/:
post:
summary: Sync project metadata (batch)
requestBody:
content:
application/json:
schema:
type: object
properties:
projects:
type: array
items:
$ref: '#/components/schemas/ProjectSync'
/api/v1/projects/by-local-uuid/{local_uuid}/:
get:
summary: Resolve local UUID to cloud project
parameters:
- name: machine_uuid
in: query
required: true
schema:
type: string
format: uuid
components:
schemas:
ProjectRegistration:
type: object
properties:
cloud_uuid:
type: string
format: uuid
local_project_uuid:
type: string
format: uuid
registration_status:
type: string
enum: [registered, pending_verification, synced]
registered_at:
type: string
format: date-time
Django Model Changes
File: backend/tenants/models.py
class Project(TenantModel):
"""Extended with cloud registration fields."""
# Existing fields...
# Registration fields (J.15.6)
registration_status = models.CharField(
max_length=20,
choices=[
('unregistered', 'Unregistered'),
('registered', 'Registered'),
('pending_verification', 'Pending Verification'),
('synced', 'Synced'),
],
default='unregistered',
db_index=True,
)
local_project_uuid = models.UUIDField(
null=True,
blank=True,
db_index=True,
help_text="Local UUID from coditect-core projects.db"
)
first_registered_at = models.DateTimeField(null=True, blank=True)
last_sync_at = models.DateTimeField(null=True, blank=True)
# Project metadata (from local detection)
content_hash = models.CharField(max_length=64, blank=True, db_index=True)
primary_language = models.CharField(max_length=50, blank=True)
framework = models.CharField(max_length=50, blank=True)
project_type = models.CharField(
max_length=20,
choices=[
('standalone', 'Standalone'),
('submodule', 'Submodule'),
('monorepo', 'Monorepo'),
],
default='standalone',
)
parent_project = models.ForeignKey(
'self',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='subprojects',
)
class ProjectMachineRegistration(TenantModel):
"""Track which machines have registered a project."""
project = models.ForeignKey(
Project,
on_delete=models.CASCADE,
related_name='machine_registrations',
)
machine_uuid = models.UUIDField(db_index=True)
local_project_uuid = models.UUIDField()
root_path = models.TextField()
registered_at = models.DateTimeField(auto_now_add=True)
last_seen_at = models.DateTimeField(auto_now=True)
class Meta:
unique_together = [('project', 'machine_uuid')]
Client Implementation
File: scripts/core/cloud_project_client.py
The local client handles:
- Offline queue for failed registrations
- Retry with exponential backoff
- Local UUID → Cloud UUID resolution cache
- Batch sync for efficiency
Sequence Diagram
Security Model
- JWT Authentication: Bearer token with tenant context
- Tenant Isolation:
django-multitenantauto-filters by tenant - Machine Verification:
machine_uuidmust match registered machines - Rate Limiting: 100 requests/minute per tenant
- Audit Log: All registrations logged to
audit_logtable
Consequences
Positive
- Cross-device sync: Same project linked across machines
- Team visibility: Projects visible in cloud dashboard
- Offline support: Queue-and-sync pattern maintains reliability
- Backward compatible: Existing projects continue to work
Negative
- Migration required: Existing local projects need registration
- Complexity increase: New sync logic to maintain
- Storage overhead: Additional tables in cloud DB
Neutral
- Requires network for initial registration (then cached)
- Machine UUID dependency from ADR-058
Implementation
Phase 1: API Layer (J.15.6.1)
- Create ViewSet and serializers
- Add endpoints to URL configuration
- Implement tenant isolation
Phase 2: Model Layer (J.15.6.2)
- Add model fields to Project
- Create ProjectMachineRegistration
- Generate and run migrations
Phase 3: Client Integration (J.15.6.3)
- Implement CloudProjectClient
- Add offline queue support
- Integrate with project_registration.py
Phase 4: Infrastructure (J.15.6.4)
- Deploy to GKE staging
- Add monitoring and alerts
- Update API documentation
Phase 5: Testing (J.15.6.5)
- Unit tests for all endpoints
- Integration tests for sync flow
- Performance tests for batch operations