Files
Jonathan Miller 11eb1e2649
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 3s
Generic: Repo Health / Access control (pull_request) Successful in 3s
Universal: Auto Version Bump / Version Bump (push) Failing after 5s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 6s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 36s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 37s
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Has been cancelled
Platform: moko-platform CI / CI Summary (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Generic: Repo Health / Release configuration (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (push) Has been cancelled
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
chore(release): bump to 09.23.00 — plugin commands, audit query, version fix
Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-31 14:18:26 -05:00

160 lines
4.8 KiB
PHP

<?php
declare(strict_types=1);
/**
* Checkpoint Manager - Manages checkpoints for recovery operations
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: MokoPlatform.Enterprise.Checkpoint
* INGROUP: MokoPlatform.Enterprise
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
* PATH: /lib/Enterprise/CheckpointManager.php
* BRIEF: Checkpoint manager for resumable operations
*
* @package MokoPlatform\Enterprise
* @version 04.00.04
* @author moko-platform Team
* @license GPL-3.0-or-later
*/
namespace MokoEnterprise;
use DateTime;
use DateTimeZone;
use Throwable;
/**
* Manages checkpoints for recovery operations.
*
* Features:
* - Save/load checkpoint state
* - Automatic timestamp tracking
* - Checkpoint listing and cleanup
* - JSON-based state persistence
*
* Example:
* ```php
* $manager = new CheckpointManager('.checkpoints');
* $manager->saveCheckpoint('operation', ['step' => 1, 'data' => 'value']);
* $state = $manager->loadCheckpoint('operation');
* ```
*/
class CheckpointManager
{
private string $checkpointDir;
public const VERSION = '09.23.00';
/**
* Initialize checkpoint manager.
*
* @param string $checkpointDir Directory to store checkpoints
*/
public function __construct(string $checkpointDir = '.checkpoints')
{
$this->checkpointDir = $checkpointDir;
// Create checkpoint directory if it doesn't exist
if (!is_dir($this->checkpointDir)) {
if (!mkdir($this->checkpointDir, 0755, true) && !is_dir($this->checkpointDir)) {
throw new RecoveryError("Failed to create checkpoint directory: {$this->checkpointDir}");
}
}
}
/**
* Save a checkpoint.
*
* @param string $name Checkpoint name
* @param array<string, mixed> $state State to save
* @return string Path to checkpoint file
* @throws RecoveryError
*/
public function saveCheckpoint(string $name, array $state): string
{
$timestamp = (new DateTime('now', new DateTimeZone('UTC')))->format('Ymd_His');
$checkpointFile = "{$this->checkpointDir}/{$name}_{$timestamp}.json";
try {
$json = json_encode($state, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR);
file_put_contents($checkpointFile, $json, LOCK_EX);
error_log("Checkpoint saved: {$checkpointFile}");
return $checkpointFile;
} catch (Throwable $e) {
error_log("Failed to save checkpoint: {$e->getMessage()}");
throw new RecoveryError("Checkpoint save failed: {$e->getMessage()}");
}
}
/**
* Load the most recent checkpoint for a name.
*
* @param string $name Checkpoint name
* @return array<string, mixed>|null Checkpoint state or null if not found
*/
public function loadCheckpoint(string $name): ?array
{
$checkpoints = glob("{$this->checkpointDir}/{$name}_*.json");
if ($checkpoints === false || empty($checkpoints)) {
return null;
}
// Sort by filename (which includes timestamp) to get latest
sort($checkpoints);
$latest = end($checkpoints);
try {
$json = file_get_contents($latest);
if ($json === false) {
error_log("Failed to read checkpoint: {$latest}");
return null;
}
$state = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
error_log("Checkpoint loaded: {$latest}");
return $state;
} catch (Throwable $e) {
error_log("Failed to load checkpoint: {$e->getMessage()}");
return null;
}
}
/**
* List available checkpoints.
*
* @param string|null $name Filter by checkpoint name (optional)
* @return array<string> List of checkpoint file paths
*/
public function listCheckpoints(?string $name = null): array
{
$pattern = $name ? "{$this->checkpointDir}/{$name}_*.json" : "{$this->checkpointDir}/*.json";
$checkpoints = glob($pattern);
return $checkpoints !== false ? $checkpoints : [];
}
/**
* Clean up old checkpoints.
*
* @param string|null $name Filter by checkpoint name (optional)
* @param int $keepLatest Number of latest checkpoints to keep
*/
public function cleanupCheckpoints(?string $name = null, int $keepLatest = 5): void
{
$checkpoints = $this->listCheckpoints($name);
sort($checkpoints);
if (count($checkpoints) > $keepLatest) {
$toRemove = array_slice($checkpoints, 0, -$keepLatest);
foreach ($toRemove as $checkpoint) {
unlink($checkpoint);
error_log("Removed old checkpoint: {$checkpoint}");
}
}
}
}