@@ -193,6 +193,11 @@ function apply_filters( string $hook_name, $value ) // phpcs:ignore Generic.Cod
193193 $ run ('git add local.txt && git commit -m local ' , $ tmp . '/demo@unpushed ' );
194194 symlink ($ tmp . '/demo@active ' , $ site_tmp . '/wp-content/plugins/demo-active ' );
195195
196+ mkdir ($ tmp . '/demo@broken-marker/target ' , 0755 , true );
197+ file_put_contents ($ tmp . '/demo@broken-marker/.git ' , 'gitdir: ' . $ primary . '/.git/worktrees/demo@broken-marker ' . "\n" );
198+ file_put_contents ($ tmp . '/demo@broken-marker/Cargo.toml ' , "[package] \nname = \"demo \"\nversion = \"0.1.0 \"\n" );
199+ file_put_contents ($ tmp . '/demo@broken-marker/target/artifact.bin ' , str_repeat ('broken ' , 128 ));
200+
196201 $ workspace = new \DataMachineCode \Workspace \Workspace ();
197202
198203 echo "=== smoke-worktree-cleanup-artifacts === \n" ;
@@ -218,6 +223,7 @@ function apply_filters( string $hook_name, $value ) // phpcs:ignore Generic.Cod
218223 $ assert (true , in_array ('demo@clean ' , $ bounded_handles , true ), 'bounded dry-run surfaces clean worktree ' );
219224 $ assert (true , in_array ('demo@dirty ' , $ bounded_handles , true ), 'bounded dry-run surfaces dirty worktree (deferred safety probes) ' );
220225 $ assert (true , in_array ('demo@unpushed ' , $ bounded_handles , true ), 'bounded dry-run surfaces unpushed worktree (deferred safety probes) ' );
226+ $ assert (true , in_array ('demo@broken-marker ' , $ bounded_handles , true ), 'bounded dry-run surfaces stale-marker worktree for artifact review ' );
221227 foreach ( $ plan ['candidates ' ] ?? array () as $ candidate ) {
222228 $ assert (true , (bool ) ( $ candidate ['safety_probes_deferred ' ] ?? false ), 'bounded candidate ' . ( $ candidate ['handle ' ] ?? '? ' ) . ' is flagged safety_probes_deferred ' );
223229 }
@@ -301,19 +307,33 @@ function apply_filters( string $hook_name, $value ) // phpcs:ignore Generic.Cod
301307 $ assert (false , is_dir ($ tmp . '/demo@clean/target ' ), 'apply-plan removes clean artifact directory ' );
302308 $ assert (true , is_dir ($ tmp . '/demo@dirty/target ' ), 'apply-plan revalidation skips dirty worktree even when bounded plan flagged it ' );
303309 $ assert (true , is_dir ($ tmp . '/demo@unpushed/target ' ), 'apply-plan revalidation skips unpushed worktree even when bounded plan flagged it ' );
310+ $ assert (true , is_dir ($ tmp . '/demo@broken-marker/target ' ), 'apply-plan revalidation skips stale-marker worktree without force ' );
304311 $ assert (true , is_dir ($ tmp . '/demo@clean ' ), 'apply-plan leaves worktree directory in place ' );
305312
306313 $ apply_skip_reasons = array_column ($ apply ['skipped ' ] ?? array (), 'reason_code ' , 'handle ' );
307314 $ assert ('dirty_worktree ' , $ apply_skip_reasons ['demo@dirty ' ] ?? '' , 'apply-plan revalidation skips dirty rows with explicit reason ' );
308315 $ assert ('unpushed_commits ' , $ apply_skip_reasons ['demo@unpushed ' ] ?? '' , 'apply-plan revalidation skips unpushed rows with explicit reason ' );
316+ $ assert ('stale_worktree_marker ' , $ apply_skip_reasons ['demo@broken-marker ' ] ?? '' , 'apply-plan revalidation gives stale marker recovery reason without force ' );
309317
310- $ force_plan = $ workspace ->worktree_cleanup_artifacts (array ( 'dry_run ' => true , 'exhaustive ' => true , 'force ' => true ));
318+ $ force_plan = $ workspace ->worktree_cleanup_artifacts (array ( 'dry_run ' => true , 'safety_probes ' => true , 'force ' => true ));
311319 $ force_handles = array_column ($ force_plan ['candidates ' ] ?? array (), 'handle ' );
312- $ assert (true , in_array ('demo@dirty ' , $ force_handles , true ), 'force permits dirty artifact candidate (exhaustive) ' );
313- $ assert (true , in_array ('demo@unpushed ' , $ force_handles , true ), 'force permits unpushed artifact candidate (exhaustive) ' );
320+ $ assert (true , in_array ('demo@dirty ' , $ force_handles , true ), 'force permits dirty artifact candidate with bounded safety probes ' );
321+ $ assert (true , in_array ('demo@unpushed ' , $ force_handles , true ), 'force permits unpushed artifact candidate with bounded safety probes ' );
322+ $ assert (true , in_array ('demo@broken-marker ' , $ force_handles , true ), 'force permits stale-marker artifact candidate when path safety is validated ' );
323+ $ force_by_handle = array_column ($ force_plan ['candidates ' ] ?? array (), null , 'handle ' );
314324 $ force_skip_reasons = array_column ($ force_plan ['skipped ' ] ?? array (), 'reason_code ' , 'handle ' );
315325 $ assert ('active_symlink_target ' , $ force_skip_reasons ['demo@active ' ] ?? '' , 'force still protects active symlink target ' );
316326
327+ $ broken_force_plan = array ( 'candidates ' => array ( $ force_by_handle ['demo@broken-marker ' ] ?? array () ) );
328+ $ broken_force_apply = $ workspace ->worktree_cleanup_artifacts (array ( 'apply_plan ' => $ broken_force_plan , 'force ' => true ));
329+ $ assert (false , is_wp_error ($ broken_force_apply ), 'force apply-plan for stale-marker artifact returns report ' );
330+ $ assert (1 , (int ) ( $ broken_force_apply ['summary ' ]['removed_artifacts ' ] ?? 0 ), 'force apply-plan removes stale-marker reconstructable artifact ' );
331+ $ removed_by_handle = array_column ($ broken_force_apply ['removed ' ] ?? array (), null , 'handle ' );
332+ $ assert ('profile_artifacts_stale_worktree_marker ' , $ removed_by_handle ['demo@broken-marker ' ]['reason_code ' ] ?? '' , 'force apply-plan records explicit stale-marker recovery reason ' );
333+ $ assert (true , str_contains ($ removed_by_handle ['demo@broken-marker ' ]['metadata_reconciliation_hint ' ] ?? '' , 'reconcile-metadata --dry-run ' ), 'force apply-plan points to metadata reconciliation after removal ' );
334+ $ assert (false , is_dir ($ tmp . '/demo@broken-marker/target ' ), 'force apply-plan removes stale-marker artifact directory ' );
335+ $ assert (true , is_dir ($ tmp . '/demo@broken-marker ' ), 'force apply-plan leaves stale-marker worktree directory in place ' );
336+
317337 if ($ failures > 0 ) {
318338 echo "\nFAILURES: {$ failures }/ {$ total }\n" ;
319339 exit (1 );
0 commit comments