WorkflowMergeAuditTrail.java
package org.hammer.audio.workflow.history;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import org.hammer.audio.workflow.merge.WorkflowMergeResolution;
import org.hammer.audio.workflow.store.CommitMetadata;
/** Encodes deterministic merge provenance through the established commit-message contract. */
final class WorkflowMergeAuditTrail {
private WorkflowMergeAuditTrail() {
throw new UnsupportedOperationException("Utility class");
}
static CommitMetadata metadata(
PreviewWorkflowMergeCommand command,
List<WorkflowMergeResolution> resolutions,
CommitMetadata requested) {
Objects.requireNonNull(command, "command");
Objects.requireNonNull(resolutions, "resolutions");
Objects.requireNonNull(requested, "requested");
return new CommitMetadata(
requested.author(),
message(command, resolutions, requested.message()),
requested.timestamp());
}
static String message(
PreviewWorkflowMergeCommand command,
List<WorkflowMergeResolution> resolutions,
String userMessage) {
StringBuilder audit = new StringBuilder(requireText(userMessage, "userMessage"));
audit.append("\n\n[workflow-merge]\n");
append(audit, "targetBranch", command.targetBranch());
append(audit, "remoteBranch", command.remoteBranch());
append(audit, "base", command.baseCommit().value());
append(audit, "local", command.localCommit().value());
append(audit, "remote", command.remoteCommit().value());
List<WorkflowMergeResolution> ordered =
resolutions.stream()
.sorted(Comparator.comparing(WorkflowMergeResolution::conflictId))
.toList();
for (int index = 0; index < ordered.size(); index++) {
WorkflowMergeResolution resolution = ordered.get(index);
String prefix = "resolution." + index + ".";
append(audit, prefix + "conflict", resolution.conflictId());
append(audit, prefix + "choice", resolution.choice().name());
if (resolution.customValue() != null) {
append(audit, prefix + "custom", resolution.customValue());
}
}
return audit.toString();
}
private static void append(StringBuilder target, String key, String value) {
target.append(key).append('=').append(escape(value)).append('\n');
}
private static String escape(String value) {
return value
.replace("\\", "\\\\")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("=", "\\=");
}
private static String requireText(String value, String name) {
if (value == null || value.isBlank()) {
throw new IllegalArgumentException(name + " must not be blank");
}
return value;
}
}