๐งน Cleanup Domains
๐ Overviewโ
The Cleanup Domains job removes orphaned and inactive Lightning Domains that fail SSL validation or remain non-active for extended periods. It runs every 6 hours, identifies domains created 7+ days ago with non-active status or SSL issues, verifies current Cloudflare status, and deletes domains that remain inactive for 14+ days. This ensures database hygiene and prevents accumulation of failed domain provisioning attempts.
Complete Flow:
- Cron Initialization:
queue-manager/crons/domains/cleanup.js - Service Processing:
queue-manager/services/domains/cleanup.js - Queue Definition:
queue-manager/queues/domains/cleanup.js
Execution Pattern: Scheduled (every 6 hours)
Queue Name: domains_cleanup
Environment Flag: QM_DOMAINS_CLEANUP=true (in index.js)
๐ Complete Processing Flowโ
sequenceDiagram
participant CRON as Cron Schedule<br/>(every 6 hours)
participant SERVICE as Cleanup Service
participant LD_DB as Lightning<br/>Domains DB
participant QUEUE as Bull Queue
participant PROCESSOR as Job Processor
participant CF_API as Cloudflare API
participant FUNNEL_DB as Funnels DB
CRON->>SERVICE: cleanup()
SERVICE->>SERVICE: Calculate 7 days ago:<br/>nDaysAgo = now - 7 days
SERVICE->>LD_DB: Find inactive domains:<br/>- (status โ 'active' OR ssl.status โ 'active')<br/>- created_at < 7 days ago
LD_DB-->>SERVICE: Inactive/problematic domains
alt Domains found
loop Each domain
SERVICE->>QUEUE: Add job: {domain}
end
loop Each queued domain
QUEUE->>PROCESSOR: Process cleanup
alt Missing cf_id
PROCESSOR->>PROCESSOR: Log error, skip
end
PROCESSOR->>CF_API: GET /custom_hostnames/{cf_id}
CF_API-->>PROCESSOR: Domain details OR 404
alt Domain not found in Cloudflare
PROCESSOR->>LD_DB: deleteOne: Remove orphaned record
PROCESSOR->>FUNNEL_DB: reviewAndSetStatusForDomain
PROCESSOR-->>QUEUE: done()
else Domain found but status changed
alt Status or SSL status changed
PROCESSOR->>LD_DB: updateOne: Sync from Cloudflare
alt New status not active
PROCESSOR->>FUNNEL_DB: reviewAndSetStatusForDomain
end
end
else Domain still inactive
alt Created > 14 days ago
PROCESSOR->>CF_API: DELETE /custom_hostnames/{cf_id}
CF_API-->>PROCESSOR: Deletion success
PROCESSOR->>LD_DB: deleteOne: Remove from database
PROCESSOR->>FUNNEL_DB: reviewAndSetStatusForDomain
else Created 7-14 days ago
PROCESSOR->>PROCESSOR: Skip (placeholder for notification)
end
end
PROCESSOR-->>QUEUE: done()
end
end
๐ Source Filesโ
1. Cron Initializationโ
File: queue-manager/crons/domains/cleanup.js
Purpose: Schedule domain cleanup every 6 hours
Cron Pattern: 0 */6 * * * (every 6 hours at minute 0)
Initialization:
const cleanup = require('../../services/domains/cleanup');
const cron = require('node-cron');
const logger = require('../../utilities/logger');
let inProgress = false;
exports.start = async () => {
try {
cron.schedule('0 */6 * * *', async () => {
if (!inProgress) {
inProgress = true;
await cleanup();
inProgress = false;
}
});
} catch (err) {
logger.error({ initiator: 'QM/domains/cleanup', error: err });
}
};
In-Progress Lock: Prevents overlapping executions.
Why Every 6 Hours?
- Balances cleanup frequency with API usage
- 7-14 day grace period means hourly checks unnecessary
- Reduces Cloudflare API load
2. Service Processing (THE INACTIVE DOMAIN DETECTION)โ
File: queue-manager/services/domains/cleanup.js
Purpose: Find domains with inactive status or SSL issues created 7+ days ago
Key Functions:
- Calculate 7-day lookback window
- Query domains with non-active status or SSL issues
- Queue all matching domains for processing
Main Processing Function:
const { default: axios } = require('axios');
const Queue = require('../../queues/domains/cleanup');
const LightningDomain = require('../../models/lightning-domain');
const logger = require('../../utilities/logger');
module.exports = async () => {
try {
const nDaysAgo = new Date();
//setting to 7 days ago
nDaysAgo.setDate(nDaysAgo.getDate() - 7);
// Handle the promise returned by LightningDomain.find
await LightningDomain.find({
$or: [
{ status: { $ne: 'active' } }, // Domain not active
{ 'ssl.status': { $ne: 'active' } }, // SSL not active
],
created_at: { $lt: nDaysAgo }, // Created over 7 days ago
})
.then(async domains => {
// If domains are found, proceed with the queue
if (domains.length) {
const queue = await Queue.start();
for (const domain of domains) {
queue.add(
{ domain },
{
attempts: 3,
},
);
}
}
logger.log({
initiator: 'QM/domains/cleanup',
message: 'Domain Cleanup service processed.',
});
})
.catch(err => {
// Log the error if the database query failed
logger.error({
initiator: 'QM/domains/cleanup',
message: 'Error fetching domains for cleanup',
error: err,
});
});
} catch (err) {
logger.error({ initiator: 'QM/domains/cleanup', error: err });
}
};
3. Queue Processor (THE MULTI-STAGE CLEANUP LOGIC)โ
File: queue-manager/queues/domains/cleanup.js
Purpose: Verify Cloudflare status, sync changes, delete old inactive domains
Key Functions:
- Fetch current Cloudflare domain status
- Handle orphaned domains (not found in Cloudflare)
- Sync status changes from Cloudflare
- Delete domains inactive for 14+ days
- Update funnel status for affected domains
Main Processor:
const QueueWrapper = require('../../common/queue-wrapper');
const LightningDomain = require('../../models/lightning-domain');
const logger = require('../../utilities/logger');
const { reviewAndSetStatusForDomain } = require('../../utilities/funnelDomainStatusUtility');
const axios = require('axios');
const processCb = async (job, done) => {
try {
let { domain } = job.data;
// Validate domain has cf_id
if (!domain.cf_id) {
logger.error({
initiator: 'QM/domains/cleanup',
message: 'Incorrect domain provided',
});
}
// Fetch current Cloudflare status
let newDomain = await axios.get(
`https://api.cloudflare.com/client/v4/zones/${process.env.LD_CLOUDFLARE_ZONE_ID}/custom_hostnames/${domain.cf_id}`,
{ headers: { Authorization: 'Bearer ' + process.env.CLOUDFLARE_API_KEY } },
);
// Handle orphaned domains (not found in Cloudflare)
if (newDomain.data?.success === false) {
// delete from db
await LightningDomain.deleteOne({ _id: domain._id });
await reviewAndSetStatusForDomain({ domain_id: domain._id });
done();
return;
} else {
newDomain = newDomain.data?.result;
// Sync status changes from Cloudflare
if (
newDomain &&
(domain.status != newDomain.status || domain.ssl.status != newDomain.ssl.status)
) {
await LightningDomain.updateOne({ _id: domain._id }, { ...newDomain });
if (newDomain.status != 'active') {
await reviewAndSetStatusForDomain({ domain_id: domain._id });
}
done();
return;
}
}
// Delete domains inactive for 14+ days
const nDaysAgo = new Date();
//setting to 7 days ago
nDaysAgo.setDate(nDaysAgo.getDate() - 14);
if (domain.created_at < nDaysAgo) {
//remove from db and cloudflare
let response = await axios.delete(
`https://api.cloudflare.com/client/v4/zones/${process.env.LD_CLOUDFLARE_ZONE_ID}/custom_hostnames/${domain.cf_id}`,
{
headers: {
Authorization: `Bearer ${process.env.CLOUDFLARE_API_KEY}`,
},
},
);
response = response.data;
if (response?.success) {
await LightningDomain.deleteOne({ _id: domain._id });
await reviewAndSetStatusForDomain({ domain_id: domain._id });
}
} else if (!domain.sent_idle_notification) {
// send notification (placeholder - not implemented)
}
done();
} catch (err) {
logger.error({
initiator: 'QM/domains/cleanup',
error: err,
api_error: err?.response?.data?.errors?.[0]?.message || undefined,
});
done(err);
}
};
let queue;
exports.start = async () => {
try {
if (!queue) queue = QueueWrapper(`domains_cleanup`, 'global', { processCb });
return Promise.resolve(queue)
.then(result => {
logger.log({
initiator: 'QM/queues/domains/cleanup',
message: 'Queue resolved:' + result,
});
return result;
})
.catch(error => {
logger.error({
initiator: 'QM/queues/domains/cleanup',
message: 'Error resolving queue:' + error,
});
throw error;
});
} catch (err) {
logger.log({
initiator: 'QM/queues/domains/cleanup',
message: 'Error while initializing domain validity queue:' + err.message,
error: err,
});
}
};
๐๏ธ Collections Usedโ
lightning_domainsโ
- Operations: Find (query inactive domains), Update (sync status from Cloudflare), Delete (remove orphaned/expired domains)
- Model:
shared/models/lightning-domain.js - Usage Context: Clean up failed or abandoned domain provisioning
Query Criteria (Inactive Domains):
{
$or: [
{ status: { $ne: 'active' } }, // Domain status not active
{ 'ssl.status': { $ne: 'active' } } // SSL status not active
],
created_at: { $lt: nDaysAgo } // Created over 7 days ago
}
Update Operation (Sync Status):
await LightningDomain.updateOne(
{ _id: domain._id },
{ ...newDomain }, // Spread Cloudflare domain object
);
Delete Operations:
// Orphaned domain (not in Cloudflare)
await LightningDomain.deleteOne({ _id: domain._id });
// Inactive for 14+ days
await LightningDomain.deleteOne({ _id: domain._id });
Key Fields:
status: Domain status ('active','pending','pending_validation','blocked')ssl.status: SSL certificate status ('active','pending','pending_validation','failed')created_at: Domain creation timestampcf_id: Cloudflare custom hostname IDsent_idle_notification: Boolean flag for notification tracking (unused)
funnels (Indirect via Utility)โ
- Operations: Update (via
reviewAndSetStatusForDomainutility) - Model:
shared/models/funnel.js - Usage Context: Set funnels to test mode when domain is deleted
Utility Function: utilities/funnelDomainStatusUtility.js
Purpose: Updates all funnels using the deleted domain to prevent broken live funnels
๐ง Job Configurationโ
Cron Scheduleโ
'0 */6 * * *'; // Every 6 hours at minute 0 (12am, 6am, 12pm, 6pm)
Why 6-Hour Interval?
- 7-14 day grace periods make frequent checks unnecessary
- Balances cleanup with API usage
- Low urgency (abandoned domains can wait)
Grace Periodsโ
7-Day Grace Period (Service Query):
const nDaysAgo = new Date();
nDaysAgo.setDate(nDaysAgo.getDate() - 7);
- Domains inactive for 7+ days are queued for cleanup
- Allows time for SSL validation and DNS propagation
14-Day Grace Period (Processor Deletion):
const nDaysAgo = new Date();
nDaysAgo.setDate(nDaysAgo.getDate() - 14);
- Domains inactive for 14+ days are permanently deleted
- Additional 7-day buffer after initial cleanup check
Job Settingsโ
queue.add(
{ domain }, // Full domain document
{
attempts: 3, // Retry up to 3 times
},
);
Job Data:
domain: Complete Lightning Domain document
Queue Configurationโ
QueueWrapper(`domains_cleanup`, 'global', { processCb });
Queue Name: domains_cleanup
Redis Scope: global (shared across queue manager instances)
๐ Processing Logic - Detailed Flowโ
1. Inactive Domain Identificationโ
Query Logic:
{
$or: [
{ status: { $ne: 'active' } },
{ 'ssl.status': { $ne: 'active' } }
],
created_at: { $lt: nDaysAgo }
}
Inactive Statuses:
| Status | Meaning | Action |
|---|---|---|
pending | DNS propagation in progress | Wait 7-14 days |
pending_validation | Awaiting domain verification | Wait 7-14 days |
blocked | Cloudflare blocked domain | Delete after 14 days |
moved | Domain moved to another zone | Delete (orphaned) |
SSL Statuses:
| SSL Status | Meaning | Action |
|---|---|---|
pending | Certificate issuance in progress | Wait 7-14 days |
pending_validation | Awaiting domain validation | Wait 7-14 days |
failed | Certificate issuance failed | Delete after 14 days |
Why 7-Day Threshold?
- SSL validation can take 1-3 days
- DNS propagation can take 24-48 hours
- 7 days provides generous buffer for legitimate delays
2. Cloudflare Status Verificationโ
Fetch Current Status:
let newDomain = await axios.get(
`https://api.cloudflare.com/client/v4/zones/${process.env.LD_CLOUDFLARE_ZONE_ID}/custom_hostnames/${domain.cf_id}`,
{ headers: { Authorization: 'Bearer ' + process.env.CLOUDFLARE_API_KEY } },
);
Response Scenarios:
Success (Domain Found):
{
success: true,
result: {
id: 'cf123...',
hostname: 'custom.example.com',
status: 'active', // May have changed
ssl: {
status: 'active' // May have changed
}
}
}
Failure (Domain Not Found):
{
success: false,
errors: [
{
code: 1003,
message: 'custom hostname not found'
}
]
}
3. Orphaned Domain Handlingโ
Detection:
if (newDomain.data?.success === false) {
// Domain not found in Cloudflare = orphaned record
}
Why Orphaned?
- Domain deleted from Cloudflare manually
- Cloudflare zone transferred/deleted
- API sync issue during creation
Cleanup Action:
await LightningDomain.deleteOne({ _id: domain._id });
await reviewAndSetStatusForDomain({ domain_id: domain._id });
Why Delete Immediately?
- No Cloudflare record = no way to resolve
- Prevents database clutter
- Updates affected funnels
4. Status Synchronizationโ
Detect Status Changes:
if (newDomain && (domain.status != newDomain.status || domain.ssl.status != newDomain.ssl.status)) {
// Status changed since last check
}
Sync from Cloudflare:
await LightningDomain.updateOne({ _id: domain._id }, { ...newDomain });
Update Funnels if Still Inactive:
if (newDomain.status != 'active') {
await reviewAndSetStatusForDomain({ domain_id: domain._id });
}
Why Sync?
- Domain may have become active since last validation job
- Prevents premature deletion of domains in progress
- Keeps database in sync with Cloudflare
5. 14-Day Deletion Logicโ
Check Domain Age:
const nDaysAgo = new Date();
nDaysAgo.setDate(nDaysAgo.getDate() - 14);
if (domain.created_at < nDaysAgo) {
// Domain created 14+ days ago and still inactive = delete
}
Cloudflare Deletion:
let response = await axios.delete(
`https://api.cloudflare.com/client/v4/zones/${process.env.LD_CLOUDFLARE_ZONE_ID}/custom_hostnames/${domain.cf_id}`,
{ headers: { Authorization: `Bearer ${process.env.CLOUDFLARE_API_KEY}` } },
);
Database Deletion (Only if Cloudflare Deletion Succeeds):
if (response?.success) {
await LightningDomain.deleteOne({ _id: domain._id });
await reviewAndSetStatusForDomain({ domain_id: domain._id });
}
Why 14 Days?
- Generous grace period for slow SSL validation
- Allows manual intervention for edge cases
- Prevents accidental deletion of in-progress domains
6. Idle Notification (Placeholder)โ
Unimplemented Feature:
else if (!domain.sent_idle_notification) {
// send notification (placeholder - not implemented)
}
Intended Behavior:
- Notify user domain is inactive (7-14 days)
- Warn of impending deletion
- Provide resolution steps
Current State: Comment only, no implementation.
๐จ Error Handlingโ
Common Error Scenariosโ
Missing cf_idโ
Scenario: Domain record without Cloudflare ID
Handling:
if (!domain.cf_id) {
logger.error({
initiator: 'QM/domains/cleanup',
message: 'Incorrect domain provided',
});
}
Result: Error logged, processing continues (no return statement - bug).
Impact: May cause Cloudflare API errors downstream.
Cloudflare API Errorsโ
Domain Not Found (404):
Scenario: Orphaned domain already deleted from Cloudflare
Result: Database record deleted, funnels updated, job succeeds.
Rate Limit Exceeded:
Scenario: Too many API calls in short period
Result: Job fails, retries up to 3 times.
Database Errorsโ
Connection Lost:
Scenario: MongoDB connection lost during deletion
Result: Exception thrown, job fails, retries up to 3 times.
Deletion Conflict:
Scenario: Domain already deleted by concurrent process
Result: deleteOne succeeds (0 documents deleted), no error.
No Retry Logic for Cloudflare Callsโ
Unlike Cancel Job: No inline retry logic for Cloudflare API calls.
Bull Retries Only: Relies on Bull queue's 3-attempt retry mechanism.
Potential Issue: Transient Cloudflare errors may cause premature job failure.
๐ Monitoring & Loggingโ
Success Loggingโ
Service-Level:
logger.log({
initiator: 'QM/domains/cleanup',
message: 'Domain Cleanup service processed.',
});
Queue Initialization:
logger.log({
initiator: 'QM/queues/domains/cleanup',
message: 'Queue resolved:' + result,
});
Error Loggingโ
Service Query Errors:
logger.error({
initiator: 'QM/domains/cleanup',
message: 'Error fetching domains for cleanup',
error: err,
});
Processor Errors:
logger.error({
initiator: 'QM/domains/cleanup',
error: err,
api_error: err?.response?.data?.errors?.[0]?.message || undefined,
});
Missing cf_id:
logger.error({
initiator: 'QM/domains/cleanup',
message: 'Incorrect domain provided',
});
Performance Metricsโ
- Service Query Time: < 100ms (indexed queries)
- Cloudflare API Latency: 200-500ms per request (GET + DELETE)
- Job Processing Time: 1-3 seconds per domain
- Typical Volume: 5-50 domains per run (varies by inactive domain accumulation)
๐ Integration Pointsโ
Triggers This Jobโ
- Cron Schedule: Every 6 hours automatically
- Manual Trigger: Via API endpoint (if QM_HOOKS=true)
External Dependenciesโ
- Cloudflare API: Custom Hostnames GET and DELETE endpoints
- Zone ID:
process.env.LD_CLOUDFLARE_ZONE_ID - API Key:
process.env.CLOUDFLARE_API_KEY - Rate Limit: 1,200 requests per 5 minutes
- Zone ID:
Jobs That Depend On Thisโ
- Domain Validation: May re-validate domains cleaned up by this job
- Domain Cancellation: Similar deletion logic (could be consolidated)
Utilities Usedโ
reviewAndSetStatusForDomain: Updates funnel status when domain is deleted
โ ๏ธ Important Notesโ
Side Effectsโ
- โ ๏ธ Permanent Deletion: Removes domains from Cloudflare AND database after 14 days
- โ ๏ธ Orphaned Domain Cleanup: Immediately deletes domains not found in Cloudflare
- โ ๏ธ Funnel Impact: Sets all funnels using deleted domains to test mode
- โ ๏ธ Status Sync: Updates database with current Cloudflare status
Performance Considerationsโ
- 6-Hour Polling: Low frequency, suitable for cleanup tasks
- No Batch Operations: Processes domains sequentially via queue
- Indexed Queries: Ensure indexes on
status,ssl.status,created_at - API Usage: 2 API calls per domain (GET + DELETE)
Business Logicโ
Why Two Grace Periods?
- 7 Days: Initial check for problematic domains
- 14 Days: Final deletion threshold
- Provides 7-day buffer for status sync and manual intervention
Why Delete Orphaned Domains Immediately?
- No Cloudflare record = unrecoverable
- Prevents database bloat
- No user impact (domain already gone from Cloudflare)
Why Sync Status Before Deleting?
- Domain may have become active since last validation
- Prevents deletion of domains that resolved
- Reduces false positives
Why No Notification Implementation?
- Placeholder for future feature
- Would warn users before deletion
- Currently relying on grace periods only
Maintenance Notesโ
- Grace Periods: 7-day and 14-day thresholds hardcoded
- Idle Notification: Not implemented (placeholder code exists)
- Missing cf_id Handling: Incomplete (logs error but continues)
- Database Indexes: Ensure composite index on
(status, ssl.status, created_at)
Code Quality Issuesโ
Bug: Missing cf_id Not Handled:
if (!domain.cf_id) {
logger.error({ message: 'Incorrect domain provided' });
}
// No return statement - processing continues with invalid domain
Impact: Cloudflare API call will fail with null cf_id.
Fix:
if (!domain.cf_id) {
logger.error({ message: 'Incorrect domain provided' });
done(new Error('Missing cf_id'));
return;
}
Bug: Comment Mismatch:
const nDaysAgo = new Date();
//setting to 7 days ago
nDaysAgo.setDate(nDaysAgo.getDate() - 14); // Actually 14 days
Impact: Confusing comment, but logic is correct.
๐งช Testingโ
Manual Triggerโ
# Via API (if QM_HOOKS=true)
POST http://localhost:6002/api/trigger/domains/cleanup
Simulate Orphaned Domainโ
// Create domain in database without Cloudflare record
const domain = await LightningDomain.create({
hostname: 'orphaned.example.com',
cf_id: 'nonexistent-cf-id',
status: 'pending',
ssl: { status: 'pending' },
created_at: new Date(Date.now() - 8 * 24 * 60 * 60 * 1000), // 8 days ago
});
// Trigger cleanup
await cleanup();
// Verify deletion
const deleted = await LightningDomain.findById(domain._id);
console.log('Orphaned domain deleted:', !deleted); // Should be true
Simulate Status Changeโ
// Create inactive domain
const domain = await LightningDomain.create({
hostname: 'test.example.com',
cf_id: 'valid-cf-id',
status: 'pending',
ssl: { status: 'pending' },
created_at: new Date(Date.now() - 8 * 24 * 60 * 60 * 1000),
});
// Manually activate domain in Cloudflare (use Cloudflare dashboard or API)
// Trigger cleanup
await cleanup();
// Verify status synced (not deleted)
const synced = await LightningDomain.findById(domain._id);
console.log('Status synced:', synced.status); // Should be 'active'
console.log('Domain preserved:', !!synced); // Should be true
Test 14-Day Deletionโ
// Create old inactive domain
const oldDomain = await LightningDomain.create({
hostname: 'old.example.com',
cf_id: 'old-cf-id',
status: 'pending',
ssl: { status: 'pending' },
created_at: new Date(Date.now() - 15 * 24 * 60 * 60 * 1000), // 15 days ago
});
// Trigger cleanup
await cleanup();
// Verify deletion
const deleted = await LightningDomain.findById(oldDomain._id);
console.log('Old domain deleted:', !deleted); // Should be true
Monitor Cleanup Activityโ
// Count domains eligible for cleanup
const nDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
const eligibleCount = await LightningDomain.countDocuments({
$or: [{ status: { $ne: 'active' } }, { 'ssl.status': { $ne: 'active' } }],
created_at: { $lt: nDaysAgo },
});
console.log(`${eligibleCount} domains eligible for cleanup`);
// Count domains to be deleted (14+ days)
const deletionThreshold = new Date(Date.now() - 14 * 24 * 60 * 60 * 1000);
const deletionCount = await LightningDomain.countDocuments({
$or: [{ status: { $ne: 'active' } }, { 'ssl.status': { $ne: 'active' } }],
created_at: { $lt: deletionThreshold },
});
console.log(`${deletionCount} domains marked for deletion`);
Verify Funnel Status Updateโ
// Setup: Create funnel with inactive domain
const domain = await LightningDomain.create({
hostname: 'test.example.com',
cf_id: 'test-cf-id',
status: 'pending',
created_at: new Date(Date.now() - 15 * 24 * 60 * 60 * 1000),
});
const funnel = await Funnel.create({
name: 'Test Funnel',
domain_id: domain._id,
status: 'live',
});
// Trigger cleanup (should delete domain)
await cleanup();
// Verify funnel set to test mode
const updatedFunnel = await Funnel.findById(funnel._id);
console.log('Funnel status:', updatedFunnel.status); // Should be 'test'
Test Cloudflare Syncโ
// Find domain with status mismatch
const domain = await LightningDomain.findOne({
status: 'pending',
created_at: { $lt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) },
});
// Check current Cloudflare status
const cfDomain = await axios.get(
`https://api.cloudflare.com/client/v4/zones/${process.env.LD_CLOUDFLARE_ZONE_ID}/custom_hostnames/${domain.cf_id}`,
{ headers: { Authorization: 'Bearer ' + process.env.CLOUDFLARE_API_KEY } },
);
console.log('DB status:', domain.status);
console.log('CF status:', cfDomain.data.result.status);
console.log('Mismatch:', domain.status !== cfDomain.data.result.status);
// Trigger cleanup
await cleanup();
// Verify sync
const updated = await LightningDomain.findById(domain._id);
console.log('Status synced:', updated.status === cfDomain.data.result.status);
Job Type: Scheduled + Queued
Execution Frequency: Every 6 hours
Average Duration: 1-3 seconds per domain
Status: Active