ADR-067: Time-Controlled Licensing System
Status
Accepted | January 12, 2026
Context
CODITECT is transitioning from an internal development framework to a commercial product requiring formal licensing:
- Pilot Users - 90-day trial access for early adopters and evaluation
- Subscription Users - Monthly/annual paid licenses
- Enterprise Customers - Multi-seat licenses with centralized management
Current State
CODITECT already has foundational components for licensing:
| Component | Location | Purpose |
|---|---|---|
machine-id.json | ~/.coditect/machine-id.json | Hardware-based UUID for device binding |
/orient command | Session start | Ideal validation checkpoint |
api.coditect.ai | Cloud API | License server endpoint |
| ADR-053 | Cloud sync | Tenant/user association |
| ADR-003 | License validation | Hybrid online/offline strategy |
| ADR-055 | Container licensing | Docker/Workstation patterns |
Requirements
- Security - Prevent unauthorized use while avoiding false positives
- Usability - Support offline work (airplane, poor connectivity)
- Flexibility - Different tiers with varying restrictions
- Manageability - Enterprise admin visibility and control
- Graceful Degradation - Clear messaging when license expires
Decision
Implement a hybrid local+server licensing system with cryptographically signed license files, device binding, and configurable grace periods.
Architecture Overview
┌─────────────────────────────────────────────────────────────────────────────┐
│ CODITECT TIME-CONTROLLED LICENSING │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ LOCAL INSTALLATION │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ~/.coditect/ │ │
│ │ ├── machine-id.json # Hardware UUID (existing) │ │
│ │ ├── license/ # NEW: License directory │ │
│ │ │ ├── license.json # Signed license file │ │
│ │ │ ├── validation-cache.json # Server validation cache │ │
│ │ │ └── offline-log.json # Offline period tracking │ │
│ │ └── config/ │ │
│ │ └── licensing.json # License configuration │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ VALIDATION FLOW │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ /orient (Session Start) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────┐ │ │
│ │ │ Load Local │ │ │
│ │ │ License File │ │ │
│ │ └────────┬────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────┐ │ │
│ │ │ Verify Signature│ ← Ed25519 public key (embedded) │ │
│ │ └────────┬────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────┐ │ │
│ │ │ Check Machine │ ← Compare with machine-id.json │ │
│ │ │ Binding │ │ │
│ │ └────────┬────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────┐ │ │
│ │ │ Check │ │ │
│ │ │ Expiration │ │ │
│ │ └────────┬────────┘ │ │
│ │ │ │ │
│ │ ┌────┴────┐ │ │
│ │ │ │ │ │
│ │ Valid Expired │ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ ┌───────┐ ┌───────────┐ │ │
│ │ │Server │ │ Grace │ │ │
│ │ │Check │ │ Period? │ │ │
│ │ └───┬───┘ └─────┬─────┘ │ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ Continue Degrade/Block │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │
│ CLOUD API (api.coditect.ai) │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ POST /api/v1/licenses/validate │ │
│ │ → Validate license key against tenant/user │ │
│ │ → Check seat availability │ │
│ │ → Return signed license payload │ │
│ │ │ │
│ │ POST /api/v1/licenses/refresh │ │
│ │ → Refresh existing license │ │
│ │ → Extend expiration (subscription renewals) │ │
│ │ → Return updated signed license │ │
│ │ │ │
│ │ POST /api/v1/licenses/revoke │ │
│ │ → Revoke license (admin action) │ │
│ │ → Add to revocation list │ │
│ │ │ │
│ │ GET /api/v1/licenses/revocations │ │
│ │ → Return revocation list (CRL-like) │ │
│ │ → Used for offline revocation checking │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
License File Format
File: ~/.coditect/license/license.json
{
"version": "1.0",
"license_id": "lic_01HXYZ789ABC123DEF456GHI",
"license_key": "CODITECT-PILOT-XXXX-XXXX-XXXX",
"licensee": {
"tenant_id": "tenant_01ABC123",
"user_id": "user_01DEF456",
"email": "user@company.com",
"organization": "Acme Corp"
},
"type": "pilot",
"tier": "pro",
"validity": {
"issued_at": "2026-01-12T00:00:00Z",
"expires_at": "2026-04-12T23:59:59Z",
"grace_period_days": 7
},
"binding": {
"machine_uuid": "d3d3a316-09c6-8f41-4a3f-d93e422d199c",
"hardware_hash": "sha256:d3d3a316...",
"max_devices": 2,
"device_name": "Hals-MacBook-Air-2.local"
},
"features": {
"agents": true,
"commands": true,
"skills": true,
"cloud_sync": true,
"context_watcher": true,
"max_tokens_per_day": null,
"priority_support": false
},
"offline": {
"max_offline_days": 14,
"last_server_check": "2026-01-12T10:30:00Z",
"offline_start": null
},
"signature": {
"algorithm": "Ed25519",
"key_id": "coditect-license-key-2026-01",
"value": "base64-encoded-signature..."
}
}
License Types and Tiers
┌─────────────────────────────────────────────────────────────────────────────┐
│ LICENSE TYPE MATRIX │
├─────────────────┬───────────┬────────────┬────────────┬────────────────────┤
│ Aspect │ Pilot │ Pro │ Team │ Enterprise │
├─────────────────┼───────────┼────────────┼────────────┼────────────────────┤
│ Duration │ 90 days │ Monthly/ │ Annual │ Custom │
│ │ │ Annual │ │ │
├─────────────────┼───────────┼────────────┼────────────┼────────────────────┤
│ Grace Period │ 7 days │ 7 days │ 3 days │ Configurable │
│ (after expiry) │ │ │ │ (0-14 days) │
├─────────────────┼───────────┼────────────┼────────────┼────────────────────┤
│ Offline │ 7 days │ 14 days │ 7 days │ Configurable │
│ Tolerance │ │ │ │ (0-30 days) │
├─────────────────┼───────────┼────────────┼────────────┼────────────────────┤
│ Server Check │ Daily │ Daily │ Daily │ Configurable │
│ Interval │ │ │ │ (hourly-weekly) │
├─────────────────┼───────────┼────────────┼────────────┼────────────────────┤
│ Max Devices │ 1 │ 2 │ Per-seat │ Unlimited │
│ │ │ │ │ (per license) │
├─────────────────┼───────────┼────────────┼────────────┼────────────────────┤
│ Expiration │ Warning │ Warning │ Degraded │ Configurable │
│ Behavior │ → Block │ → Degraded │ → Block │ │
├─────────────────┼───────────┼────────────┼────────────┼────────────────────┤
│ Seat Management │ N/A │ N/A │ Per-team │ Centralized │
├─────────────────┼───────────┼────────────┼────────────┼────────────────────┤
│ Cloud Sync │ Yes │ Yes │ Yes │ Yes + Audit │
├─────────────────┼───────────┼────────────┼────────────┼────────────────────┤
│ Price │ Free │ $29/mo │ $99/mo │ Custom │
│ │ │ $290/yr │ per seat │ │
└─────────────────┴───────────┴────────────┴────────────┴────────────────────┘
Expiration Behavior Flow
┌─────────────────────────────────────────────────────────────────────────────┐
│ EXPIRATION BEHAVIOR STATE MACHINE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ LICENSE STATES │
│ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌────────┐ │ │
│ │ │ ACTIVE │────▶│ WARNING │────▶│ GRACE │────▶│ EXPIRED│ │ │
│ │ │ │ │ (7 days) │ │ PERIOD │ │ │ │ │
│ │ └───────────┘ └───────────┘ └───────────┘ └────────┘ │ │
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │
│ │ ▼ ▼ ▼ ▼ │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ BEHAVIOR │ │ │
│ │ ├──────────┬────────────┬───────────────┬────────────────────┤ │ │
│ │ │ ACTIVE │ WARNING │ GRACE │ EXPIRED │ │ │
│ │ ├──────────┼────────────┼───────────────┼────────────────────┤ │ │
│ │ │ Full │ Full │ Degraded │ Blocked or │ │ │
│ │ │ features │ features + │ features │ Free tier │ │ │
│ │ │ │ banner │ (no sync, │ (tier-dependent) │ │ │
│ │ │ │ │ basic agents)│ │ │ │
│ │ └──────────┴────────────┴───────────────┴────────────────────┘ │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │
│ TRANSITION TRIGGERS │
│ │
│ ACTIVE → WARNING: 7 days before expiration │
│ WARNING → GRACE: License expiration date reached │
│ GRACE → EXPIRED: Grace period exhausted (0-14 days based on tier) │
│ │
│ ANY → ACTIVE: License renewed/extended │
│ ANY → REVOKED: Admin revocation (immediate) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Offline Tolerance System
┌─────────────────────────────────────────────────────────────────────────────┐
│ OFFLINE TOLERANCE FLOW │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ SESSION START (/orient) │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Try Server │ │
│ │ Validation │ │
│ └────────┬────────┘ │
│ │ │
│ ┌────┴────┐ │
│ │ │ │
│ Success Network │
│ │ Error │
│ │ │ │
│ ▼ ▼ │
│ ┌───────┐ ┌───────────────────────────────────────┐ │
│ │Update │ │ OFFLINE MODE │ │
│ │Cache │ │ │ │
│ │ │ │ 1. Load cached license │ │
│ │ │ │ 2. Check offline duration │ │
│ │ │ │ 3. Verify not revoked (cached CRL) │ │
│ │ │ │ 4. Log offline period start │ │
│ └───┬───┘ │ │ │
│ │ │ ┌────────────────────────────┐ │ │
│ │ │ │ Offline Duration Check │ │ │
│ │ │ └────────────┬───────────────┘ │ │
│ │ │ │ │ │
│ │ │ ┌─────────┴─────────┐ │ │
│ │ │ │ │ │ │
│ │ │ Within Limit Exceeded │ │
│ │ │ │ │ │ │
│ │ │ ▼ ▼ │ │
│ │ │ ┌─────────┐ ┌─────────┐ │ │
│ │ │ │ ALLOW │ │ DEGRADE │ │ │
│ │ │ │ (banner)│ │ or BLOCK│ │ │
│ │ │ └─────────┘ └─────────┘ │ │
│ │ │ │ │
│ │ └───────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Continue Session │
│ │
│ ──────────────────────────────────────────────────────────────────────── │
│ │
│ OFFLINE TRACKING FILE: ~/.coditect/license/offline-log.json │
│ │
│ { │
│ "offline_periods": [ │
│ { │
│ "started_at": "2026-01-10T08:00:00Z", │
│ "ended_at": "2026-01-11T14:30:00Z", │
│ "duration_hours": 30.5, │
│ "reason": "network_unavailable" │
│ } │
│ ], │
│ "current_offline_start": null, │
│ "total_offline_hours_30d": 42.5, │
│ "offline_abuse_flag": false │
│ } │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Device Binding Using machine-id.json
The existing machine-id.json provides hardware-based device identification:
┌─────────────────────────────────────────────────────────────────────────────┐
│ DEVICE BINDING FLOW │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ FIRST ACTIVATION (New Device) │
│ │
│ 1. User enters license key │
│ │ │
│ ▼ │
│ 2. Read machine-id.json │
│ { │
│ "machine_uuid": "d3d3a316-09c6-8f41-4a3f-d93e422d199c", │
│ "hardware_hash": "sha256:d3d3a316...", │
│ "hostname": "Hals-MacBook-Air-2.local" │
│ } │
│ │ │
│ ▼ │
│ 3. POST /api/v1/licenses/activate │
│ { │
│ "license_key": "CODITECT-PILOT-XXXX-XXXX-XXXX", │
│ "machine_uuid": "d3d3a316-09c6-8f41-4a3f-d93e422d199c", │
│ "hardware_hash": "sha256:d3d3a316...", │
│ "device_name": "Hals-MacBook-Air-2.local" │
│ } │
│ │ │
│ ▼ │
│ 4. Server checks: │
│ - License key valid? │
│ - Device count within max_devices? │
│ - Not already activated on different device? (if max_devices=1) │
│ │ │
│ ▼ │
│ 5. Server returns signed license bound to machine_uuid │
│ │ │
│ ▼ │
│ 6. Save to ~/.coditect/license/license.json │
│ │
│ ──────────────────────────────────────────────────────────────────────── │
│ │
│ SUBSEQUENT VALIDATION │
│ │
│ 1. Load ~/.coditect/license/license.json │
│ │ │
│ ▼ │
│ 2. Compare license.binding.machine_uuid with machine-id.json │
│ │ │
│ ┌────┴────┐ │
│ │ │ │
│ Match Mismatch │
│ │ │ │
│ ▼ ▼ │
│ Continue Block + Message: │
│ "License activated on different device. │
│ Deactivate old device or contact support." │
│ │
│ ──────────────────────────────────────────────────────────────────────── │
│ │
│ DEVICE TRANSFER (User Request) │
│ │
│ 1. User runs: /license deactivate │
│ │ │
│ ▼ │
│ 2. POST /api/v1/licenses/deactivate │
│ - Removes machine_uuid binding │
│ - Frees up device slot │
│ │ │
│ ▼ │
│ 3. User can activate on new device │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Cryptographic Security
┌─────────────────────────────────────────────────────────────────────────────┐
│ LICENSE SIGNING AND VERIFICATION │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ SIGNING (Server-Side) │
│ │
│ ┌─────────────────┐ │
│ │ License Payload │ │
│ │ (JSON without │ │
│ │ signature) │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Canonical JSON │ ← Sort keys, no whitespace, UTF-8 │
│ │ Serialize │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Ed25519 Sign │ ← Private key in Cloud KMS (HSM-protected) │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Base64 Encode │ │
│ │ Signature │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ Append to license.signature.value │
│ │
│ ──────────────────────────────────────────────────────────────────────── │
│ │
│ VERIFICATION (Client-Side) │
│ │
│ ┌─────────────────┐ │
│ │ Load License │ │
│ │ File │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Extract │ │
│ │ Signature │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Rebuild │ ← Same canonical serialization │
│ │ Payload │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Ed25519 Verify │ ← Public key embedded in framework │
│ └────────┬────────┘ │
│ │ │
│ ┌────┴────┐ │
│ │ │ │
│ Valid Invalid │
│ │ │ │
│ ▼ ▼ │
│ Continue Block + Message: │
│ "License file has been tampered with. │
│ Please re-activate your license." │
│ │
│ ──────────────────────────────────────────────────────────────────────── │
│ │
│ KEY MANAGEMENT │
│ │
│ Private Key: │
│ - Stored in Google Cloud KMS (HSM-protected) │
│ - Key: projects/coditect-citus-prod/locations/us-central1/ │
│ keyRings/coditect-licensing/cryptoKeys/license-signing-key │
│ - Rotation: Annual (key_id in signature tracks version) │
│ │
│ Public Key: │
│ - Embedded in framework: ~/.coditect/config/license-public-keys.json │
│ - Published: https://coditect.ai/.well-known/license-keys.json │
│ - Multiple keys supported for rotation │
│ │
│ Key File Format: │
│ { │
│ "keys": [ │
│ { │
│ "key_id": "coditect-license-key-2026-01", │
│ "algorithm": "Ed25519", │
│ "public_key": "base64-encoded-public-key...", │
│ "valid_from": "2026-01-01T00:00:00Z", │
│ "valid_until": "2027-12-31T23:59:59Z" │
│ } │
│ ] │
│ } │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
API Endpoint Specifications
POST /api/v1/licenses/activate
Activate a license key on a new device.
Request:
{
"license_key": "CODITECT-PILOT-XXXX-XXXX-XXXX",
"machine_uuid": "d3d3a316-09c6-8f41-4a3f-d93e422d199c",
"hardware_hash": "sha256:d3d3a316...",
"device_name": "Hals-MacBook-Air-2.local"
}
Response (Success):
{
"success": true,
"license": { /* Full signed license.json content */ },
"message": "License activated successfully"
}
Response (Error - Max Devices):
{
"success": false,
"error": "max_devices_exceeded",
"message": "License already activated on maximum devices (2). Deactivate a device first.",
"activated_devices": [
{"device_name": "Old-MacBook-Pro", "activated_at": "2026-01-01T10:00:00Z"}
]
}
POST /api/v1/licenses/validate
Validate and refresh a license (called periodically).
Request:
{
"license_id": "lic_01HXYZ789ABC123DEF456GHI",
"machine_uuid": "d3d3a316-09c6-8f41-4a3f-d93e422d199c"
}
Response:
{
"valid": true,
"status": "active",
"days_remaining": 45,
"license": { /* Updated signed license if changed */ },
"revocation_list_hash": "sha256:abc123...",
"next_check_recommended": "2026-01-13T10:30:00Z"
}
POST /api/v1/licenses/deactivate
Deactivate license on current device (free up slot).
Request:
{
"license_id": "lic_01HXYZ789ABC123DEF456GHI",
"machine_uuid": "d3d3a316-09c6-8f41-4a3f-d93e422d199c"
}
Response:
{
"success": true,
"message": "Device deactivated. 1 device slot now available.",
"remaining_devices": 1
}
GET /api/v1/licenses/revocations
Get list of revoked licenses (for offline checking).
Response:
{
"updated_at": "2026-01-12T12:00:00Z",
"revocations": [
{
"license_id": "lic_REVOKED123",
"revoked_at": "2026-01-10T08:00:00Z",
"reason": "payment_failed"
}
],
"hash": "sha256:abc123..."
}
Integration with /orient Command
# In commands/orient.py (simplified)
async def validate_license_on_orient():
"""Called at start of /orient command."""
license_manager = LicenseManager()
# Load and verify license
try:
license = license_manager.load_license()
except LicenseNotFoundError:
print("No license found. Run '/license activate XXXX-XXXX-XXXX' to activate.")
return False
except SignatureVerificationError:
print("License file corrupted. Run '/license refresh' to re-download.")
return False
# Check machine binding
machine_id = load_machine_id()
if license.binding.machine_uuid != machine_id.machine_uuid:
print("License not valid for this device.")
return False
# Check expiration
status = license_manager.get_status()
if status == LicenseStatus.EXPIRED:
if license_manager.in_grace_period():
days_remaining = license_manager.grace_days_remaining()
print(f"License expired. Grace period: {days_remaining} days remaining.")
print("Renew at https://coditect.ai/renew")
# Continue with degraded features
else:
print("License expired and grace period exhausted.")
print("Renew at https://coditect.ai/renew")
return False
elif status == LicenseStatus.WARNING:
days_remaining = license_manager.days_until_expiry()
print(f"License expires in {days_remaining} days.")
print("Renew at https://coditect.ai/renew")
# Try server validation (non-blocking if offline)
try:
await license_manager.server_validate()
except NetworkError:
if license_manager.offline_days() > license.offline.max_offline_days:
print(f"Offline limit exceeded ({license.offline.max_offline_days} days).")
print("Please connect to internet to continue.")
return False
else:
offline_days = license_manager.offline_days()
print(f"Offline mode ({offline_days}/{license.offline.max_offline_days} days).")
return True
Renewal Mechanisms
┌─────────────────────────────────────────────────────────────────────────────┐
│ LICENSE RENEWAL FLOWS │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ AUTOMATIC RENEWAL (Subscription) │
│ │
│ 1. Stripe webhook: invoice.paid │
│ │ │
│ ▼ │
│ 2. Backend extends license expiration │
│ │ │
│ ▼ │
│ 3. Next /orient → server validation → updated license downloaded │
│ │
│ ──────────────────────────────────────────────────────────────────────── │
│ │
│ MANUAL RENEWAL (Pilot → Paid) │
│ │
│ 1. User visits https://coditect.ai/upgrade │
│ │ │
│ ▼ │
│ 2. Enters payment info → Stripe processes │
│ │ │
│ ▼ │
│ 3. Backend upgrades license tier + extends expiration │
│ │ │
│ ▼ │
│ 4. User runs: /license refresh │
│ │ │
│ ▼ │
│ 5. New signed license downloaded │
│ │
│ ──────────────────────────────────────────────────────────────────────── │
│ │
│ CLI COMMANDS │
│ │
│ /license status Show current license status │
│ /license activate KEY Activate new license key │
│ /license refresh Re-download license from server │
│ /license deactivate Deactivate on this device (free up slot) │
│ /license info Show detailed license information │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Tampering Prevention
| Attack Vector | Mitigation |
|---|---|
| Edit license.json | Ed25519 signature verification fails |
| Copy license to other machine | machine_uuid binding mismatch |
| Clock manipulation | Server validation compares server time |
| Replay old license | License ID + device binding tracked server-side |
| Man-in-the-middle | HTTPS + signature verification |
| Reverse engineer public key | Ed25519 is asymmetric - private key in HSM |
| Patch framework code | Root-owned protected installation (ADR-055) |
File Locations
| File | Purpose |
|---|---|
~/.coditect/license/license.json | Signed license file |
~/.coditect/license/validation-cache.json | Cached server response |
~/.coditect/license/offline-log.json | Offline period tracking |
~/.coditect/config/license-public-keys.json | Embedded public keys |
~/.coditect/config/licensing.json | License configuration |
~/.coditect/machine-id.json | Hardware UUID (existing) |
Consequences
Positive
- Revenue Protection - Prevents unauthorized use and license sharing
- Offline Support - Users can work without internet for configured period
- Graceful Degradation - Clear warnings before blocking, grace periods
- Enterprise-Ready - Centralized management, audit trails, configurable policies
- Cryptographically Secure - HSM-protected signing keys, Ed25519 signatures
- Device Binding - Uses existing machine-id.json infrastructure
- User-Friendly - Automatic validation in /orient, clear CLI commands
Negative
- Network Dependency - Requires periodic server check (mitigated by offline tolerance)
- Implementation Complexity - Multiple states, flows, and edge cases
- User Friction - Activation step required for new users
- Infrastructure Cost - Cloud KMS, API endpoints, revocation checking
Risks and Mitigations
| Risk | Mitigation |
|---|---|
| Server outage blocks users | 14-day offline tolerance + cached validation |
| Key compromise | Key rotation mechanism + revocation list |
| False positive blocks | Grace periods + clear error messages + support escalation |
| Clock drift issues | Use server time, allow +/- 1 hour tolerance |
| Database sync issues | Local cache is authoritative within offline period |
Implementation Recommendations
Phase 1: Foundation (Week 1-2)
- Create
~/.coditect/license/directory structure - Implement license file format and signature verification
- Create
/licenseCLI commands (status, activate, refresh) - Integrate validation check into /orient
Phase 2: Backend API (Week 3-4)
- Implement
/api/v1/licenses/*endpoints - Set up Cloud KMS signing key
- Create admin dashboard for license management
- Implement Stripe webhook for subscription renewal
Phase 3: Offline Support (Week 5)
- Implement offline tolerance tracking
- Create revocation list endpoint and caching
- Add offline abuse detection
- Test network failure scenarios
Phase 4: Enterprise Features (Week 6-8)
- Seat management API
- Centralized admin portal
- Audit logging
- Custom policy configuration
Related Documents
- ADR-003: License Validation Strategy - Hybrid validation approach
- ADR-053: Cloud Context Sync Architecture - Tenant/user association
- ADR-055: Licensed Docker Container Schema - Container licensing
- ADR-057: CODITECT Core Initial Setup - machine-id.json
- ADR-058: Machine-Specific Session Logs - Device identification
References
- Ed25519 Signature Specification: RFC 8032
- Google Cloud KMS: Asymmetric Signing
- ULID Specification: github.com/ulid/spec
Author: senior-architect agent Reviewed By: Pending Approved: January 12, 2026