From fa4c9ae4f2bba39655f5fa24e5ba435454d0b9f6 Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Sun, 24 May 2026 09:52:57 -0400 Subject: [PATCH 1/3] fix: control active cleanup child jobs --- inc/Cli/Commands/WorkspaceCommand.php | 58 +++++++++++++++++++++------ tests/smoke-worktree-cleanup-cli.php | 9 +++++ 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/inc/Cli/Commands/WorkspaceCommand.php b/inc/Cli/Commands/WorkspaceCommand.php index 33ff5174..8ad95a83 100644 --- a/inc/Cli/Commands/WorkspaceCommand.php +++ b/inc/Cli/Commands/WorkspaceCommand.php @@ -666,22 +666,56 @@ private function control_cleanup_run_job( string $operation, int $job_id, array return; } - $input = array( 'job_id' => $job_id ); - if ( 'resume' === $operation ) { - $input['force'] = ! empty( $assoc_args['force'] ); - } else { - $input['reason'] = 'cleanup_cancelled'; + $target_job_ids = $this->cleanup_run_control_job_ids( $operation, $job_id ); + $results = array(); + foreach ( $target_job_ids as $target_job_id ) { + $input = array( 'job_id' => $target_job_id ); + if ( 'resume' === $operation ) { + $input['force'] = ! empty( $assoc_args['force'] ); + } else { + $input['reason'] = 'cleanup_cancelled'; + } + + $result = $ability->execute( $input ); + if ( ! ( $result['success'] ?? false ) ) { + WP_CLI::error( (string) ( $result['error'] ?? 'Cleanup run control failed.' ) ); + return; + } + $results[] = $result; } - $result = $ability->execute( $input ); - if ( ! ( $result['success'] ?? false ) ) { - WP_CLI::error( (string) ( $result['error'] ?? 'Cleanup run control failed.' ) ); - return; + $output = $results[0] ?? array( 'success' => true, 'job_id' => $job_id ); + $output['run_id'] = $this->cleanup_run_id( $job_id ); + $output['state'] = 'resume' === $operation ? 'running' : 'cancelled'; + $output['controlled_job_ids'] = $target_job_ids; + $output['results'] = $results; + $this->render_cleanup_control_result( $output, $assoc_args ); + } + + /** + * Resolve which Data Machine jobs should be controlled for a job-backed cleanup run. + * + * @param string $operation Cleanup control operation. + * @param int $job_id Cleanup parent job ID. + * @return array + */ + private function cleanup_run_control_job_ids( string $operation, int $job_id ): array { + $output = $this->cleanup_run_evidence_store()->read( $this->cleanup_run_id( $job_id ), true, true ); + if ( $output instanceof \WP_Error ) { + return array( $job_id ); } - $result['run_id'] = $this->cleanup_run_id( $job_id ); - $result['state'] = 'resume' === $operation ? 'running' : 'cancelled'; - $this->render_cleanup_control_result( $result, $assoc_args ); + $children = (array) ( $output['evidence']['children'] ?? array() ); + $processing_ids = array_map( 'intval', (array) ( $children['processing_job_ids'] ?? array() ) ); + $failed_ids = array_map( 'intval', (array) ( $children['failed_job_ids'] ?? array() ) ); + $pending_ids = array_map( 'intval', (array) ( $children['pending_job_ids'] ?? array() ) ); + + if ( 'resume' === $operation ) { + $child_targets = array_values( array_unique( array_filter( array_merge( $processing_ids, $failed_ids ) ) ) ); + return array() !== $child_targets ? $child_targets : array( $job_id ); + } + + return array_values( array_unique( array_filter( array_merge( array( $job_id ), $pending_ids, $processing_ids ) ) ) ); } private function render_cleanup_control_result( array $result, array $assoc_args ): void { diff --git a/tests/smoke-worktree-cleanup-cli.php b/tests/smoke-worktree-cleanup-cli.php index 7a9b378f..ac5f20af 100644 --- a/tests/smoke-worktree-cleanup-cli.php +++ b/tests/smoke-worktree-cleanup-cli.php @@ -553,9 +553,11 @@ public function execute( array $input ): array { class FakeRetryJobAbility { public array $last_input = array(); + public array $inputs = array(); public function execute( array $input ): array { $this->last_input = $input; + $this->inputs[] = $input; return array( 'success' => true, 'job_id' => (int) $input['job_id'], @@ -567,9 +569,11 @@ public function execute( array $input ): array { class FakeFailJobAbility { public array $last_input = array(); + public array $inputs = array(); public function execute( array $input ): array { $this->last_input = $input; + $this->inputs[] = $input; return array( 'success' => true, 'job_id' => (int) $input['job_id'], @@ -714,11 +718,16 @@ public function execute( array $input ): array { WP_CLI::$logs = array(); WP_CLI::$successes = array(); $command->cleanup( array( 'resume', 'cleanup-run-123' ), array( 'force' => true, 'format' => 'json' ) ); + $resume_json = json_decode( WP_CLI::$logs[0] ?? '', true ); + datamachine_code_cleanup_assert( array( 125, 127 ) === ( $resume_json['controlled_job_ids'] ?? array() ), 'cleanup resume controls active child jobs before the parent' ); datamachine_code_cleanup_assert( true === ( $retry_job_ability->last_input['force'] ?? null ), 'cleanup resume forwards force retry flag' ); + datamachine_code_cleanup_assert( array( 125, 127 ) === array_map( fn( $input ) => (int) ( $input['job_id'] ?? 0 ), $retry_job_ability->inputs ), 'cleanup resume retries processing and failed child jobs' ); WP_CLI::$logs = array(); WP_CLI::$successes = array(); $command->cleanup( array( 'cancel', 'cleanup-run-123' ), array( 'format' => 'json' ) ); + $cancel_json = json_decode( WP_CLI::$logs[0] ?? '', true ); + datamachine_code_cleanup_assert( array( 123, 125 ) === ( $cancel_json['controlled_job_ids'] ?? array() ), 'cleanup cancel controls parent and active child jobs' ); datamachine_code_cleanup_assert( 'cleanup_cancelled' === ( $fail_job_ability->last_input['reason'] ?? '' ), 'cleanup cancel fails job with cleanup cancellation reason' ); echo "\n[0] list stale output exposes disk fields\n"; From 70e2ff4dc933aefe7a19a14e676c53a93a20bf5a Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Sun, 24 May 2026 10:03:14 -0400 Subject: [PATCH 2/3] fix: satisfy cleanup control lint --- inc/Cli/Commands/WorkspaceCommand.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/inc/Cli/Commands/WorkspaceCommand.php b/inc/Cli/Commands/WorkspaceCommand.php index 8ad95a83..4cf4b997 100644 --- a/inc/Cli/Commands/WorkspaceCommand.php +++ b/inc/Cli/Commands/WorkspaceCommand.php @@ -684,7 +684,10 @@ private function control_cleanup_run_job( string $operation, int $job_id, array $results[] = $result; } - $output = $results[0] ?? array( 'success' => true, 'job_id' => $job_id ); + $output = $results[0] ?? array( + 'success' => true, + 'job_id' => $job_id, + ); $output['run_id'] = $this->cleanup_run_id( $job_id ); $output['state'] = 'resume' === $operation ? 'running' : 'cancelled'; $output['controlled_job_ids'] = $target_job_ids; From ae73f21dd311b03c36984d5f441f10ebf98cb5db Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Sun, 24 May 2026 10:08:57 -0400 Subject: [PATCH 3/3] fix: align cleanup control output assignment --- inc/Cli/Commands/WorkspaceCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/Cli/Commands/WorkspaceCommand.php b/inc/Cli/Commands/WorkspaceCommand.php index 4cf4b997..b88254af 100644 --- a/inc/Cli/Commands/WorkspaceCommand.php +++ b/inc/Cli/Commands/WorkspaceCommand.php @@ -684,7 +684,7 @@ private function control_cleanup_run_job( string $operation, int $job_id, array $results[] = $result; } - $output = $results[0] ?? array( + $output = $results[0] ?? array( 'success' => true, 'job_id' => $job_id, );