CentralOutcome.java

package network.ike.plugin.release.central;

import java.nio.file.Path;

/**
 * Outcome of the Maven Central deploy phase in the release pipeline.
 *
 * <p>Central deploys have two paths: a synchronous retry loop and a
 * detached async-bash spawn (IKE-Network/ike-issues#484). Both share
 * this outcome record. When the async path is taken,
 * {@code asyncSpawned} is {@code true} and {@code sentinelPath} +
 * {@code logPath} point to the IPC files that {@code ike:central-status}
 * reads to discover post-process completion.
 *
 * <p>Instances are immutable. Callers update state by constructing a
 * new instance via the {@code with*} helpers. {@link #initial()}
 * returns the "did not run" state — appropriate for a draft preview
 * or a release aborted before the deploy phase.
 *
 * <p>Carved out of {@code ReleaseDraftMojo} during the Phase 4
 * decomposition (IKE-Network/ike-issues#489 P1) so that downstream
 * commits can replace mojo fields with phase-returned outcomes
 * without further reshaping.
 *
 * @param succeeded      whether the Central deploy completed successfully (sync path) or was successfully spawned (async path)
 * @param attempts       number of attempts made (zero before the first try)
 * @param asyncSpawned   whether the deploy was spawned as a detached subprocess (#484)
 * @param sentinelPath   IPC sentinel file path when {@code asyncSpawned} is true, otherwise {@code null}
 * @param logPath        log file the async subprocess streams to when {@code asyncSpawned} is true, otherwise {@code null}
 * @param skipReason     human-readable reason if the phase was skipped, or {@code null} when not skipped
 * @param failureSummary human-readable summary when the phase was attempted and exhausted retries, or {@code null} when not failed
 */
public record CentralOutcome(
        boolean succeeded,
        int attempts,
        boolean asyncSpawned,
        Path sentinelPath,
        Path logPath,
        String skipReason,
        String failureSummary) {

    /**
     * Returns the initial outcome — not succeeded, zero attempts, no async spawn, no skip, no failure.
     *
     * @return the initial {@code CentralOutcome}
     */
    public static CentralOutcome initial() {
        return new CentralOutcome(false, 0, false, null, null, null, null);
    }

    /**
     * Returns a copy of this outcome with the attempt count replaced.
     *
     * @param attempts the new attempt count
     * @return a new {@code CentralOutcome} with the updated attempt count
     */
    public CentralOutcome withAttempts(int attempts) {
        return new CentralOutcome(succeeded, attempts, asyncSpawned, sentinelPath, logPath, skipReason, failureSummary);
    }

    /**
     * Returns a copy of this outcome with the success flag replaced.
     *
     * @param succeeded the new success flag
     * @return a new {@code CentralOutcome} with the updated success flag
     */
    public CentralOutcome withSucceeded(boolean succeeded) {
        return new CentralOutcome(succeeded, attempts, asyncSpawned, sentinelPath, logPath, skipReason, failureSummary);
    }

    /**
     * Returns a copy of this outcome with the {@code asyncSpawned} flag replaced.
     *
     * @param asyncSpawned the new async-spawn flag
     * @return a new {@code CentralOutcome} with the updated async-spawn flag
     */
    public CentralOutcome withAsyncSpawned(boolean asyncSpawned) {
        return new CentralOutcome(succeeded, attempts, asyncSpawned, sentinelPath, logPath, skipReason, failureSummary);
    }

    /**
     * Returns a copy of this outcome with the sentinel-file path replaced.
     *
     * @param sentinelPath the new sentinel path, or {@code null} to clear
     * @return a new {@code CentralOutcome} with the updated sentinel path
     */
    public CentralOutcome withSentinelPath(Path sentinelPath) {
        return new CentralOutcome(succeeded, attempts, asyncSpawned, sentinelPath, logPath, skipReason, failureSummary);
    }

    /**
     * Returns a copy of this outcome with the log-file path replaced.
     *
     * @param logPath the new log path, or {@code null} to clear
     * @return a new {@code CentralOutcome} with the updated log path
     */
    public CentralOutcome withLogPath(Path logPath) {
        return new CentralOutcome(succeeded, attempts, asyncSpawned, sentinelPath, logPath, skipReason, failureSummary);
    }

    /**
     * Returns a copy of this outcome with the skip reason replaced.
     *
     * @param skipReason the new skip reason, or {@code null} to clear
     * @return a new {@code CentralOutcome} with the updated skip reason
     */
    public CentralOutcome withSkipReason(String skipReason) {
        return new CentralOutcome(succeeded, attempts, asyncSpawned, sentinelPath, logPath, skipReason, failureSummary);
    }

    /**
     * Returns a copy of this outcome with the failure summary replaced.
     *
     * @param failureSummary the new failure summary, or {@code null} to clear
     * @return a new {@code CentralOutcome} with the updated failure summary
     */
    public CentralOutcome withFailureSummary(String failureSummary) {
        return new CentralOutcome(succeeded, attempts, asyncSpawned, sentinelPath, logPath, skipReason, failureSummary);
    }
}