ArchitectureCommandPort.java

package com.taxonomy.editor;

import com.taxonomy.dsl.command.ArchitectureCommand;
import com.taxonomy.dsl.command.ArchitectureDslCommands.Change;
import com.taxonomy.relations.model.RelationDecisionProjection;
import com.taxonomy.workspace.service.RepositoryContext;
import com.taxonomy.workspace.service.RepositoryScope;

import java.io.IOException;
import java.util.Objects;
import java.util.UUID;

/** Exact-context application boundary. HTTP and canvas DTOs do not enter the domain command handler. */
public interface ArchitectureCommandPort {
    record Context(String repositoryId, String workspaceScopeKey, String branch, String commit, long revision,
                   String actor, String writeMode) {
        public static Context of(RepositoryContext context, String commit) {
            return of(context, commit, 0);
        }
        public static Context version(RepositoryContext context, String commit) {
            return new Context(context.repositoryId(), RelationDecisionProjection.scopeKeyFor(context.workspaceId()),
                    context.branch(), commit, 0, context.username(), "READ_ONLY");
        }
        public static Context of(RepositoryContext context, String commit, long revision) {
            return new Context(context.repositoryId(), RelationDecisionProjection.scopeKeyFor(context.workspaceId()),
                    context.branch(), commit, revision, context.username(),
                    context.scope() == RepositoryScope.WORKSPACE ? "PRIVATE_WORKSPACE" : "READ_ONLY");
        }
    }

    sealed interface Operation permits SemanticCommand, UndoArchitectureCommand, RedoArchitectureCommand {}
    record SemanticCommand(ArchitectureCommand command) implements Operation {
        public SemanticCommand { Objects.requireNonNull(command); }
    }
    record UndoArchitectureCommand(UUID targetOperationId) implements Operation {
        public UndoArchitectureCommand { Objects.requireNonNull(targetOperationId); }
    }
    record RedoArchitectureCommand(UUID targetOperationId) implements Operation {
        public RedoArchitectureCommand { Objects.requireNonNull(targetOperationId); }
    }
    record CreateCheckpointCommand(Context context, Metadata metadata) {
        public CreateCheckpointCommand {
            if (context == null || metadata == null) throw new IllegalArgumentException("Checkpoint context and metadata are required");
        }
    }
    record CheckpointAccepted(Context context, String commandId, String commitId,
                              long fromRevision, long throughRevision, boolean commitCreated, boolean replayed) {}

    record Metadata(String commandId, String correlationId, String causationId, String rationale) {
        public Metadata {
            commandId = UUID.fromString(commandId).toString();
            correlationId = UUID.fromString(correlationId).toString();
            causationId = UUID.fromString(causationId).toString();
            if (rationale == null || rationale.isBlank() || rationale.length() > 1000) {
                throw new IllegalArgumentException("A rationale of 1–1000 characters is required");
            }
            rationale = rationale.strip().replaceAll("\\s+", " ");
            if (rationale.chars().anyMatch(Character::isISOControl)) {
                throw new IllegalArgumentException("Rationale must not contain control characters");
            }
        }
    }

    record Command(Context context, Metadata metadata, Operation operation) {
        public Command {
            Objects.requireNonNull(context);
            Objects.requireNonNull(metadata);
            Objects.requireNonNull(operation);
        }
    }
    record Preview(Context context, Change change, String kind, String targetOperationId) {}
    record Accepted(Context context, String commandId, String operationId, boolean replayed,
                    String projectionState, Change change) {}

    Preview preview(RepositoryContext context, Command command) throws IOException;
    Accepted execute(RepositoryContext context, Command command) throws IOException;
}