Phase 07: Activation & Heartbeat
Implement the device lifecycle: activate a seat, renew its lease via heartbeat, deactivate on demand, and cull zombies that stop checking in.
Files Written
Section titled “Files Written”src/Services/ActivationService.phpsrc/Services/HeartbeatService.php
ActivationService
Section titled “ActivationService”Seat Accounting
Section titled “Seat Accounting”Each license has a max_activations limit (NULL = unlimited). On every activation attempt:
- Look up the machine record by fingerprint hash.
- Idempotent re-activation — if the fingerprint already maps to an active machine for this license, return the existing machine without consuming another seat. This prevents double-counting on client retries.
- Count active machines for the license against
max_activations. - Apply the overage strategy:
| Strategy | Behaviour |
|---|---|
deny | Reject at max_activations |
allow_1_25x | Allow up to ceil(max × 1.25) |
allow_2x | Allow up to max × 2 |
Floating Leases
Section titled “Floating Leases”Machines get a lease_expires_at timestamp set to now + heartbeat_interval. The interval is filterable:
$interval = apply_filters('wplm_heartbeat_interval', 600); // secondsA machine is considered “alive” while lease_expires_at > now. When a lease expires the seat remains recorded (not auto-freed) — cull_zombies() handles cleanup separately.
HeartbeatService
Section titled “HeartbeatService”Constructor
Section titled “Constructor”public function __construct( MachineRepository $machine_repo, LicenseRepository $license_repo, ActivationLogRepository $log_repo, Fingerprint $fingerprint,)renew_lease()
Section titled “renew_lease()”Called by the POST /wplm/v1/heartbeat endpoint. Updates lease_expires_at on the machine row and logs an HEARTBEAT entry in the activation log.
cull_zombies()
Section titled “cull_zombies()”Scheduled WP-Cron job (runs every wplm_zombie_window / 2 seconds, default 600 s). Finds all machines where lease_expires_at < now - zombie_window and marks them status = inactive. The zombie window is filterable:
$zombie_window = apply_filters('wplm_zombie_window', 1200); // secondsThis means: if a client hasn’t sent a heartbeat for 20 minutes (by default), its seat is freed for another device. The two-stage gap (lease expired → zombie cull) prevents seats from being freed during momentary network outages.
Log entries
Section titled “Log entries”Both activate and heartbeat paths write an ActivationLog row with:
license_id,machine_idevent_type—activate,deactivate,heartbeat,cullip_address(VARBINARY 16, INET6_ATON)created_at