Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion inc/Cleanup/CleanupRunEvidenceStoreInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ interface CleanupRunEvidenceStoreInterface {
*
* @param string $run_id Stable cleanup run identifier.
* @param bool $include_evidence Whether to include raw evidence records.
* @param bool $include_details Whether to include verbose diagnostic details.
* @return array<string,mixed>|\WP_Error
*/
public function read( string $run_id, bool $include_evidence = false ): array|\WP_Error;
public function read( string $run_id, bool $include_evidence = false, bool $include_details = false ): array|\WP_Error;
}
104 changes: 77 additions & 27 deletions inc/Cleanup/DataMachineJobCleanupRunEvidenceStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ class DataMachineJobCleanupRunEvidenceStore implements CleanupRunEvidenceStoreIn
*
* @param string $run_id Stable cleanup run identifier.
* @param bool $include_evidence Whether to include raw evidence records.
* @param bool $include_details Whether to include verbose diagnostic details.
* @return array<string,mixed>|\WP_Error
*/
public function read( string $run_id, bool $include_evidence = false ): array|\WP_Error {
public function read( string $run_id, bool $include_evidence = false, bool $include_details = false ): array|\WP_Error {
$job_id = $this->cleanup_run_job_id( $run_id );
if ( $job_id <= 0 ) {
return new \WP_Error( 'invalid_cleanup_run_id', 'Cleanup run id must be a numeric job id or cleanup-run-<job_id>.', array( 'status' => 400 ) );
Expand All @@ -34,21 +35,26 @@ public function read( string $run_id, bool $include_evidence = false ): array|\W
$aggregate = $this->aggregate_cleanup_child_jobs( $child_jobs );
$children = $aggregate['children'];
$state = $this->cleanup_run_state( (string) ( $job['status'] ?? '' ), $children );
$output = array(
'success' => true,
'state' => $state,
'run_id' => $this->cleanup_run_id( $job_id ),
'job_id' => $job_id,
'status' => in_array( $state, array( 'children_processing', 'partial_failed' ), true ) ? $state : ( $job['status'] ?? '' ),
'created_at' => $job['created_at'] ?? '',
'completed_at' => $job['completed_at'] ?? '',
'artifact_cleanup' => $aggregate['artifact_cleanup'],
'cleanup_items' => $aggregate['cleanup_items'],
'children' => $children,

$children_for_output = ( $include_evidence || $include_details ) ? $children : $this->summarize_cleanup_children( $children );
$output = array(
'success' => true,
'state' => $state,
'run_id' => $this->cleanup_run_id( $job_id ),
'job_id' => $job_id,
'status' => in_array( $state, array( 'children_processing', 'partial_failed' ), true ) ? $state : ( $job['status'] ?? '' ),
'created_at' => $job['created_at'] ?? '',
'parent_completed_at' => $job['completed_at'] ?? '',
'artifact_cleanup' => $aggregate['artifact_cleanup'],
'cleanup_items' => $aggregate['cleanup_items'],
'children' => $children_for_output,
);

$output_aggregate = $aggregate;
$output_aggregate['children'] = $children_for_output;

if ( isset( $engine_data['system_task_result'] ) && is_array( $engine_data['system_task_result'] ) ) {
$engine_data['system_task_result'] = $this->with_cleanup_aggregate_report( $engine_data['system_task_result'], $aggregate );
$engine_data['system_task_result'] = $this->with_cleanup_aggregate_report( $engine_data['system_task_result'], $output_aggregate );
}

if ( $include_evidence ) {
Expand Down Expand Up @@ -105,15 +111,18 @@ private function aggregate_cleanup_child_jobs( array $child_jobs ): array {
'failed_by_reason' => array(),
),
'children' => array(
'batch_job_ids' => array(),
'chunk_job_ids' => array(),
'processing' => 0,
'completed' => 0,
'failed' => 0,
'running' => 0,
'total' => 0,
'statuses' => array(),
'job_ids' => array(),
'batch_job_ids' => array(),
'chunk_job_ids' => array(),
'pending_job_ids' => array(),
'processing_job_ids' => array(),
'failed_job_ids' => array(),
'processing' => 0,
'completed' => 0,
'failed' => 0,
'running' => 0,
'total' => 0,
'statuses' => array(),
'job_ids' => array(),
),
);

Expand All @@ -126,6 +135,13 @@ private function aggregate_cleanup_child_jobs( array $child_jobs ): array {
++$summary['children']['total'];
if ( $child_job_id > 0 ) {
$summary['children']['job_ids'][] = $child_job_id;
if ( 'pending' === $status ) {
$summary['children']['pending_job_ids'][] = $child_job_id;
} elseif ( 'processing' === $status ) {
$summary['children']['processing_job_ids'][] = $child_job_id;
} elseif ( str_starts_with( $status, 'failed' ) ) {
$summary['children']['failed_job_ids'][] = $child_job_id;
}
}

$this->count_cleanup_child_status( $summary['children'], $status );
Expand Down Expand Up @@ -160,12 +176,46 @@ private function aggregate_cleanup_child_jobs( array $child_jobs ): array {
$summary['cleanup_items']['freed_human'] = $this->format_bytes( $summary['cleanup_items']['bytes_reclaimed'] );
$summary['children']['batch_job_ids'] = array_values( array_unique( $summary['children']['batch_job_ids'] ) );
$summary['children']['chunk_job_ids'] = array_values( array_unique( $summary['children']['chunk_job_ids'] ) );
$summary['children']['pending_job_ids'] = array_values( array_unique( $summary['children']['pending_job_ids'] ) );
$summary['children']['processing_job_ids'] = array_values( array_unique( $summary['children']['processing_job_ids'] ) );
$summary['children']['failed_job_ids'] = array_values( array_unique( $summary['children']['failed_job_ids'] ) );
$summary['children']['job_ids'] = array_values( array_unique( $summary['children']['job_ids'] ) );
$summary['children']['running'] = (int) $summary['children']['processing'];

return $summary;
}

/**
* Return operator-focused child status without unbounded diagnostic ID lists.
*
* @param array<string,mixed> $children Full child aggregate.
* @return array<string,mixed>
*/
private function summarize_cleanup_children( array $children ): array {
$limit = 10;
$batch_ids = (array) ( $children['batch_job_ids'] ?? array() );
$chunk_ids = (array) ( $children['chunk_job_ids'] ?? array() );
$pending = (array) ( $children['pending_job_ids'] ?? array() );
$processing = (array) ( $children['processing_job_ids'] ?? array() );

return array(
'processing' => (int) ( $children['processing'] ?? 0 ),
'completed' => (int) ( $children['completed'] ?? 0 ),
'failed' => (int) ( $children['failed'] ?? 0 ),
'running' => (int) ( $children['running'] ?? 0 ),
'total' => (int) ( $children['total'] ?? 0 ),
'statuses' => (array) ( $children['statuses'] ?? array() ),
'batch_total' => count( $batch_ids ),
'chunk_total' => count( $chunk_ids ),
'failed_job_ids' => (array) ( $children['failed_job_ids'] ?? array() ),
'pending_job_ids' => array_slice( $pending, 0, $limit ),
'processing_job_ids' => array_slice( $processing, 0, $limit ),
'pending_truncated' => count( $pending ) > $limit,
'processing_truncated' => count( $processing ) > $limit,
'diagnostic_job_id_lists' => 'Re-run status with --verbose or use cleanup evidence for full child job IDs.',
);
}

/**
* Merge one chunk task result into aggregate cleanup item counters.
*
Expand Down Expand Up @@ -440,11 +490,11 @@ private function cleanup_run_job_id( string $run_id ): int {
* @return string
*/
private function format_bytes( int $bytes ): string {
$bytes = max( 0, $bytes );
$units = array( 'B', 'KiB', 'MiB', 'GiB', 'TiB' );
$max_unit = count( $units ) - 1;
$value = (float) $bytes;
$unit = 0;
$bytes = max( 0, $bytes );
$units = array( 'B', 'KiB', 'MiB', 'GiB', 'TiB' );
$max_unit = count( $units ) - 1;
$value = (float) $bytes;
$unit = 0;
while ( $value >= 1024 && $unit < $max_unit ) {
$value /= 1024;
++$unit;
Expand Down
5 changes: 4 additions & 1 deletion inc/Cli/Commands/WorkspaceCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,9 @@ public function adopt_repo( array $args, array $assoc_args ): void {
* without disabling the bounded scan. Useful when you want a small slice
* audited with full safety information.
*
* [--verbose]
* : Include full diagnostic child job ID lists in task-backed cleanup status output.
*
* [--format=<format>]
* : Output format.
* ---
Expand Down Expand Up @@ -627,7 +630,7 @@ private function render_worktree_emergency_cleanup_result_from_ability( array|\W
}

private function render_cleanup_run_status( int $job_id, array $assoc_args, bool $evidence ): void {
$output = $this->cleanup_run_evidence_store()->read( $this->cleanup_run_id( $job_id ), $evidence );
$output = $this->cleanup_run_evidence_store()->read( $this->cleanup_run_id( $job_id ), $evidence, ! empty( $assoc_args['verbose'] ) );
if ( $output instanceof \WP_Error ) {
WP_CLI::error( $output->get_error_message() );
return;
Expand Down
18 changes: 17 additions & 1 deletion tests/smoke-worktree-cleanup-cli.php
Original file line number Diff line number Diff line change
Expand Up @@ -666,14 +666,30 @@ public function execute( array $input ): array {
$status_json = json_decode( WP_CLI::$logs[0] ?? '', true );
datamachine_code_cleanup_assert( 'children_processing' === ( $status_json['state'] ?? '' ), 'cleanup status stays active while child batch is processing' );
datamachine_code_cleanup_assert( 'children_processing' === ( $status_json['status'] ?? '' ), 'cleanup status does not report parent completed while children run' );
datamachine_code_cleanup_assert( array( 125 ) === ( $status_json['children']['batch_job_ids'] ?? array() ), 'cleanup status reports child batch job ids' );
datamachine_code_cleanup_assert( '2026-05-03 00:10:00' === ( $status_json['parent_completed_at'] ?? '' ), 'cleanup status labels parent scheduler completion separately' );
datamachine_code_cleanup_assert( ! isset( $status_json['completed_at'] ), 'cleanup status does not expose parent completion as run completion' );
datamachine_code_cleanup_assert( ! isset( $status_json['children']['job_ids'] ), 'cleanup status omits full child job ids by default' );
datamachine_code_cleanup_assert( ! isset( $status_json['children']['chunk_job_ids'] ), 'cleanup status omits full child chunk job ids by default' );
datamachine_code_cleanup_assert( 1 === (int) ( $status_json['children']['batch_total'] ?? 0 ), 'cleanup status reports child batch totals by default' );
datamachine_code_cleanup_assert( 2 === (int) ( $status_json['children']['chunk_total'] ?? 0 ), 'cleanup status reports child chunk totals by default' );
datamachine_code_cleanup_assert( array( 125 ) === ( $status_json['children']['processing_job_ids'] ?? array() ), 'cleanup status reports bounded processing job ids by default' );
datamachine_code_cleanup_assert( array( 127 ) === ( $status_json['children']['failed_job_ids'] ?? array() ), 'cleanup status reports failed job ids by default' );
datamachine_code_cleanup_assert( 1 === (int) ( $status_json['children']['running'] ?? 0 ), 'cleanup status summarizes running child jobs' );
datamachine_code_cleanup_assert( ! isset( $status_json['flow_id'] ), 'cleanup status is not linked to a flow id' );
datamachine_code_cleanup_assert( 4 === (int) ( $status_json['artifact_cleanup']['planned_rows'] ?? 0 ), 'cleanup status aggregates artifact planned rows from child chunks' );
datamachine_code_cleanup_assert( 4096 === (int) ( $status_json['artifact_cleanup']['bytes_reclaimed'] ?? 0 ), 'cleanup status aggregates artifact bytes from child chunks' );
datamachine_code_cleanup_assert( 4 === (int) ( $status_json['cleanup_items']['planned_rows'] ?? 0 ), 'cleanup status aggregates planned rows from DB-backed cleanup item evidence' );
datamachine_code_cleanup_assert( 4096 === (int) ( $status_json['cleanup_items']['bytes_reclaimed'] ?? 0 ), 'cleanup status reconstructs reclaimed bytes from cleanup item evidence' );
datamachine_code_cleanup_assert( '4.0 KiB' === ( $status_json['system_task_result']['report']['freed_human'] ?? '' ), 'cleanup status replaces pending child job freed placeholder' );
datamachine_code_cleanup_assert( ! isset( $status_json['system_task_result']['children']['job_ids'] ), 'cleanup status system task result omits full child job ids by default' );

WP_CLI::$logs = array();
WP_CLI::$successes = array();
$command->cleanup( array( 'status', 'cleanup-run-123' ), array( 'format' => 'json', 'verbose' => true ) );
$verbose_status_json = json_decode( WP_CLI::$logs[0] ?? '', true );
datamachine_code_cleanup_assert( array( 124, 125, 126, 127 ) === ( $verbose_status_json['children']['job_ids'] ?? array() ), 'cleanup status --verbose exposes full child job ids' );
datamachine_code_cleanup_assert( array( 125 ) === ( $verbose_status_json['children']['batch_job_ids'] ?? array() ), 'cleanup status --verbose exposes child batch job ids' );
datamachine_code_cleanup_assert( array( 126, 127 ) === ( $verbose_status_json['children']['chunk_job_ids'] ?? array() ), 'cleanup status --verbose exposes child chunk job ids' );

WP_CLI::$logs = array();
WP_CLI::$successes = array();
Expand Down
Loading