>> $pipelines */ public function __construct(private array $pipelines) {} /** @return array> */ public function stepsFor(string $pipeline): array { if (! isset($this->pipelines[$pipeline])) { throw new InvalidArgumentException("Unknown pipeline [{$pipeline}]."); } return $this->pipelines[$pipeline]; } public function count(string $pipeline): int { return count($this->stepsFor($pipeline)); } /** * Will a run of `$pipeline` standing at `$fromStep` still carry out every * step of `$work`? * * The question a caller about to start a `$work` run has to ask about the run * already in flight, and the reason it is asked step by step rather than by * pipeline name: `plan-change` contains the whole of `address` and the whole * of `storage`, `quota` contains only the last third of `storage`, and * `restart` contains neither. A name comparison would have to be kept in step * with config/provisioning.php by hand; this reads the same lists the runner * executes. * * EVERY step, not any: the three steps of a storage change are ordered * because a quota Nextcloud enforces over a filesystem that never grew is a * promise of space the machine does not have. A run that would apply the new * quota and nothing else does not cover the work — it delivers half of it, * which is worse than delivering none. * * Steps at `$fromStep` itself count as still to come. The step there is * either about to run or running, and every step reads the machine's state * when it gets there rather than when the run was created. */ public function stillCovers(string $pipeline, int $fromStep, string $work): bool { $remaining = array_slice($this->stepsFor($pipeline), max($fromStep, 0)); foreach ($this->stepsFor($work) as $step) { if (! in_array($step, $remaining, true)) { return false; } } return true; } public function resolve(string $pipeline, int $index): ProvisioningStep { $steps = $this->stepsFor($pipeline); if (! isset($steps[$index])) { throw new InvalidArgumentException("No step at index {$index} for pipeline [{$pipeline}]."); } return app($steps[$index]); } }