WorkflowSemanticHistoryResult.java

package org.hammer.audio.workflow.history;

import java.util.List;
import java.util.Objects;
import org.hammer.audio.workflow.store.CommitId;

/**
 * Domain-semantic evidence tied to one exact authoritative workflow commit.
 *
 * @param commitId exact Git commit identity
 * @param branch branch for which the commit was projected as reachable
 * @param workflowId stable workflow identifier
 * @param workflowName human-readable workflow name
 * @param nodeIds stable node identifiers contained in the workflow
 * @param nodeTypes logical node types contained in the workflow
 * @param nodeLabels human-readable node labels contained in the workflow
 * @param properties exact workflow/node metadata entries contained in the workflow
 */
public record WorkflowSemanticHistoryResult(
    CommitId commitId,
    String branch,
    String workflowId,
    String workflowName,
    List<String> nodeIds,
    List<String> nodeTypes,
    List<String> nodeLabels,
    List<WorkflowSemanticProperty> properties) {

  public WorkflowSemanticHistoryResult {
    Objects.requireNonNull(commitId, "commitId");
    branch = requireNotBlank(branch, "branch");
    workflowId = requireNotBlank(workflowId, "workflowId");
    workflowName = requireNotBlank(workflowName, "workflowName");
    nodeIds = List.copyOf(Objects.requireNonNull(nodeIds, "nodeIds"));
    nodeTypes = List.copyOf(Objects.requireNonNull(nodeTypes, "nodeTypes"));
    nodeLabels = List.copyOf(Objects.requireNonNull(nodeLabels, "nodeLabels"));
    properties = List.copyOf(Objects.requireNonNull(properties, "properties"));
  }

  /**
   * Returns distinct metadata keys as convenience evidence without losing {@link #properties()}.
   */
  public List<String> propertyKeys() {
    return properties.stream().map(WorkflowSemanticProperty::key).distinct().sorted().toList();
  }

  /** Returns distinct metadata values as convenience evidence without losing key/value pairs. */
  public List<String> propertyValues() {
    return properties.stream().map(WorkflowSemanticProperty::value).distinct().sorted().toList();
  }

  private static String requireNotBlank(String value, String name) {
    Objects.requireNonNull(value, name);
    String normalized = value.trim();
    if (normalized.isEmpty()) {
      throw new IllegalArgumentException(name + " must not be blank");
    }
    return normalized;
  }
}