Skip to main content

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:

ProblemImpact
No cloud visibilityPlatform has no awareness of local projects
Cross-device sync impossibleSame project on different machines appears as separate projects
Team collaboration blockedCannot share project context with team members
Analytics gapNo aggregated project metrics across organization
Orphaned dataCloud task tracking without project context

Current State

Local (coditect-core):

  • projects.db with projects table (ADR-118 Tier 3)
  • Local UUID generation per machine
  • No cloud awareness

Cloud (coditect-cloud-infra):

  • Multi-tenant Django with django-multitenant
  • Project model without local linking
  • Task tracking without project association

Requirements

  1. Offline-First: Must work without network connectivity
  2. Cross-Device: Same project identifiable across machines
  3. Multi-Tenant: Tenant isolation via django-multitenant
  4. Idempotent: Re-registration safe and deterministic
  5. 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:

  1. Offline queue for failed registrations
  2. Retry with exponential backoff
  3. Local UUID → Cloud UUID resolution cache
  4. Batch sync for efficiency

Sequence Diagram

Security Model

  1. JWT Authentication: Bearer token with tenant context
  2. Tenant Isolation: django-multitenant auto-filters by tenant
  3. Machine Verification: machine_uuid must match registered machines
  4. Rate Limiting: 100 requests/minute per tenant
  5. Audit Log: All registrations logged to audit_log table

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

References