Skip to main content

ADR-061: Provisioning Strategies - License & Workstation

FieldValue
StatusAccepted
Date2026-01-09
Decision MakersHal Casteel (CEO/CTO), Architecture Team
SupersedesN/A
Related ADRsADR-003, ADR-010, ADR-009

Executive Summary

CODITECT supports three distinct provisioning strategies for licenses and workstations:

  1. Paid Flow - Standard commercial path via Stripe checkout
  2. Admin License Provision - Manual license creation for pilot/trial/comp/internal users
  3. Admin Direct Workstation - Workstation provisioning independent of license status

Key Decision: Licenses and Workstations are independent entities that can be provisioned separately. This enables flexible onboarding for pilot customers, trials, and internal testing while maintaining a clean commercial flow for paying customers.


Context and Problem Statement

CODITECT needs to support multiple customer acquisition paths:

  1. Paying Customers - Self-service via Stripe with automatic provisioning
  2. Pilot Customers - Hand-selected early adopters who need access without payment
  3. Trial Users - Time-limited evaluation access
  4. Complimentary Access - Partners, advisors, investors
  5. Internal Users - AZ1.AI team members for testing/development

Requirements

Functional:

  • Admins can provision licenses without Stripe payment
  • Admins can provision workstations without requiring a license first
  • All provisioning types use the same underlying models and services
  • Audit trail for all admin provisioning actions
  • Batch provisioning for multiple pilot customers

Non-Functional:

  • No code duplication between provisioning paths
  • Clear separation of concerns (license vs workstation)
  • Extensible for future provisioning types
  • Full audit compliance (who, what, when, why)

Decision

We implement three provisioning strategies with a decoupled architecture where licenses and workstations are independent:

Architecture Overview

┌─────────────────────────────────────────────────────────────────────────────┐
│ CODITECT PROVISIONING ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ FLOW 1: PAID (Stripe) │ │
│ │ │ │
│ │ User ──► Stripe Checkout ──► Webhook ──► License Service ──► │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────┐ │ │
│ │ │ License Created │ │ │
│ │ │ provisioning_type │ │ │
│ │ │ = "paid" │ │ │
│ │ └─────────┬───────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────┐ │ │
│ │ │ Workstation │ │ │
│ │ │ Auto-Provisioned │ │ │
│ │ │ via Celery Task │ │ │
│ │ └─────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ FLOW 2: ADMIN LICENSE PROVISION │ │
│ │ │ │
│ │ Admin ──► POST /api/v1/admin/licenses/provision/ ──► │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────┐ │ │
│ │ │ License Created │ │ │
│ │ │ provisioning_type = │ │ │
│ │ │ "pilot" | "trial" | │ │ │
│ │ │ "comp" | "internal" │ │ │
│ │ │ provisioned_by = admin_id │ │ │
│ │ │ provisioning_notes = "..." │ │ │
│ │ └─────────────┬───────────────┘ │ │
│ │ │ │ │
│ │ ▼ (if auto_provision_workstation=true) │ │
│ │ ┌─────────────────────────────┐ │ │
│ │ │ Workstation Provisioned │ │ │
│ │ │ via Celery Task │ │ │
│ │ └─────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ FLOW 3: ADMIN DIRECT WORKSTATION │ │
│ │ │ │
│ │ Admin ──► POST /api/v1/admin/workstations/provision/ ──► │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────┐ │ │
│ │ │ Workstation Created │ │ │
│ │ │ (No license required) │ │ │
│ │ │ tier = standard|power|ai │ │ │
│ │ └─────────────────────────────┘ │ │
│ │ │ │
│ │ Use Cases: │ │
│ │ - Pre-license setup for demos │ │
│ │ - Internal development/testing │ │
│ │ - Troubleshooting user issues │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘

Provisioning Type Field

License Model Extension

class License(models.Model):
# ... existing fields ...

PROVISIONING_TYPES = [
('paid', 'Paid (Stripe)'), # Standard commercial
('pilot', 'Pilot Customer'), # Early adopter program
('trial', 'Trial'), # Time-limited evaluation
('comp', 'Complimentary'), # Partners, advisors, investors
('internal', 'Internal'), # AZ1.AI team members
]

provisioning_type = models.CharField(
max_length=20,
choices=PROVISIONING_TYPES,
default='paid',
db_index=True,
help_text="How the license was provisioned"
)

provisioned_by = models.ForeignKey(
'users.User',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='provisioned_licenses',
help_text="Admin who manually provisioned (null for paid)"
)

provisioning_notes = models.TextField(
blank=True,
help_text="Admin notes explaining provisioning reason"
)

Provisioning Type Descriptions

TypeDescriptionDurationAuto-RenewUse Case
paidStandard Stripe purchaseSubscription-basedYesNormal customers
pilotPilot program participant1 year defaultNoEarly adopters, beta testers
trialTime-limited evaluation14-30 daysNoProspects evaluating product
compComplimentary accessVariesNoPartners, advisors, investors
internalInternal team member1 yearNoAZ1.AI employees, contractors

API Endpoints

Flow 1: Paid (Existing)

POST /api/v1/subscriptions/checkout/
→ Creates Stripe checkout session
→ Webhook creates license with provisioning_type="paid"
→ Auto-provisions workstation via Celery

Flow 2: Admin License Provision

POST /api/v1/admin/licenses/provision/
{
"user_email": "pilot@customer.com",
"tier": "PRO",
"provisioning_type": "pilot",
"duration_days": 365,
"max_containers": 5,
"features": ["marketplace", "analytics"],
"notes": "Pilot customer - contacted via LinkedIn",
"auto_provision_workstation": true,
"workstation_tier": "standard"
}

Response 201:
{
"license_id": "uuid",
"license_key": "CODITECT-XXXX-XXXX-XXXX",
"user_email": "pilot@customer.com",
"organization_id": "uuid",
"tier": "PRO",
"provisioning_type": "pilot",
"expiry_date": "2027-01-09T00:00:00Z",
"workstation_provisioned": true,
"workstation_id": "uuid"
}

Flow 2B: Batch Provisioning

POST /api/v1/admin/pilot/batch-provision/
{
"customers": [
{"user_email": "pilot1@company.com", "tier": "PRO"},
{"user_email": "pilot2@company.com", "tier": "ENTERPRISE"}
],
"provisioning_type": "pilot",
"duration_days": 365,
"auto_provision_workstations": true
}

Response 200:
{
"total_requested": 2,
"successful": 2,
"failed": 0,
"results": [...]
}

Flow 3: Admin Direct Workstation

POST /api/v1/admin/workstations/provision/
{
"user_email": "demo@customer.com",
"tier": "standard",
"notes": "Demo workstation for sales call",
"auto_start": false
}

Response 201:
{
"workstation_id": "ws-tenant-user",
"user_email": "demo@customer.com",
"organization_id": "uuid",
"tier": "standard",
"status": "CREATING",
"gcp_resource_name": "projects/.../workstations/ws-...",
"estimated_ready_seconds": 300
}

Decoupled Architecture

Key Principle: Independence

Licenses and Workstations are NOT tightly coupled.

EntityIdentifierCan Exist Without
Licenseorganization_idWorkstation (yes)
Workstationtenant_id + user_idLicense (yes)

Why Decoupled?

  1. Flexibility: Provision workstation before license (demos, testing)
  2. Simplicity: Each entity has clear ownership and lifecycle
  3. Resilience: License issues don't block workstation access
  4. Use Cases:
    • Demo: Workstation without license
    • API-only: License without workstation
    • Full access: Both license and workstation

Audit Trail

All admin provisioning actions are logged to AuditLog:

AuditLog.objects.create(
organization=user.organization,
user=admin_user,
action='LICENSE_PROVISIONED_ADMIN', # or WORKSTATION_PROVISIONED_ADMIN
resource_type='license', # or 'workstation'
resource_id=license_obj.id,
metadata={
'license_key': 'CODITECT-XXXX-...',
'tier': 'PRO',
'provisioning_type': 'pilot',
'duration_days': 365,
'recipient_email': 'pilot@customer.com',
'notes': 'Pilot customer - LinkedIn outreach',
}
)

Audit Actions

ActionDescription
LICENSE_PROVISIONED_ADMINAdmin manually provisioned license
LICENSE_PROVISIONED_ADMIN_BATCHBatch license provisioning
LICENSE_EXTENDED_ADMINAdmin extended license expiry
LICENSE_REVOKED_ADMINAdmin revoked license
WORKSTATION_PROVISIONED_ADMINAdmin provisioned workstation
WORKSTATION_DELETED_ADMINAdmin deleted workstation

Security Considerations

Authorization

  • All admin endpoints require IsAdminUser permission
  • Admin actions are tied to authenticated admin user
  • Audit log captures admin identity for compliance

Abuse Prevention

  1. Rate Limiting: Admin endpoints rate-limited to prevent abuse
  2. Batch Limits: Max 100 customers per batch provision
  3. Duration Caps: Max 3650 days (10 years) for manual licenses
  4. Audit Trail: All actions logged with full metadata

Consequences

Positive

  1. Pilot Flexibility: Can onboard pilot customers without Stripe integration
  2. Clear Separation: License and workstation lifecycles are independent
  3. Full Audit: Complete trail of who provisioned what and why
  4. Extensible: Easy to add new provisioning types (e.g., "education", "nonprofit")
  5. Batch Operations: Efficiently onboard multiple pilot customers

Negative

  1. Admin Responsibility: Admins must manage manual licenses (expiry, revocation)
  2. Potential Misuse: Admin could provision without proper authorization (mitigated by audit)
  3. Complexity: Three flows to maintain vs single Stripe flow

Risks and Mitigations

RiskMitigation
Admin provisions unauthorized licensesAudit trail + periodic review
Pilot licenses never convert to paidExpiry dates + conversion tracking
Orphaned workstations (no license)Admin workstation list view + cleanup

Implementation Status

ComponentStatusLocation
License provisioning_type field✅ Completelicenses/models.py
Admin license provision endpoint✅ Completeapi/v1/views/admin.py
Admin license extend endpoint✅ Completeapi/v1/views/admin.py
Admin license revoke endpoint✅ Completeapi/v1/views/admin.py
Batch provisioning endpoint✅ Completeapi/v1/views/admin.py
Pilot customers list endpoint✅ Completeapi/v1/views/admin.py
Admin workstation provision endpoint✅ Completeapi/v1/views/admin.py
Admin workstation list endpoint✅ Completeapi/v1/views/admin.py
Admin workstation delete endpoint✅ Completeapi/v1/views/admin.py
Audit logging✅ Completelicenses/models.py
Admin UI⏳ PendingTrack A.13.4

  • ADR-003: License Validation Strategy (heartbeat, offline support)
  • ADR-010: Cloud Workstations Architecture (GCP infrastructure)
  • ADR-009: Multi-Tenant SaaS Architecture (tenant isolation)

Decision Outcome

Chosen Option: Three-flow provisioning with decoupled license/workstation architecture.

This approach provides maximum flexibility for customer acquisition while maintaining clean separation of concerns and full audit compliance.


Approved By: Hal Casteel, CEO/CTO Date: 2026-01-09