package vip.mate.wiki.pipeline;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import vip.mate.wiki.model.WikiPipelineDefinitionEntity;
import vip.mate.wiki.model.WikiPipelineRunEntity;
import vip.mate.wiki.model.WikiPipelineStepRunEntity;
import vip.mate.wiki.repository.WikiPipelineRunMapper;
import vip.mate.wiki.repository.WikiPipelineStepRunMapper;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
/**
* Runs a wiki pipeline definition: creates a dedup-guarded run, executes each
* step through the matching {@link WikiStepExecutor} under the definition's
* owner agent, and records run / step status.
*
*
Run creation is idempotent: a duplicate trigger envelope collides on the
* run table's unique key and is skipped, so concurrent instances cannot spawn
* parallel runs for the same trigger.
*
* @author MateClaw Team
*/
@Slf4j
@Service
public class WikiPipelineService {
private final WikiPipelineRunMapper runMapper;
private final WikiPipelineStepRunMapper stepRunMapper;
private final ObjectMapper objectMapper;
private final Map executors;
public WikiPipelineService(WikiPipelineRunMapper runMapper,
WikiPipelineStepRunMapper stepRunMapper,
ObjectMapper objectMapper,
List executorBeans) {
this.runMapper = runMapper;
this.stepRunMapper = stepRunMapper;
this.objectMapper = objectMapper;
this.executors = new java.util.HashMap<>();
for (WikiStepExecutor e : executorBeans) {
this.executors.put(e.type(), e);
}
}
/** Outcome of an attempted run. {@code run} is null when skipped as a duplicate. */
public record RunOutcome(WikiPipelineRunEntity run, boolean duplicate) {}
/**
* Execute a definition for one trigger. Returns {@code duplicate=true} with
* a null run when the trigger envelope was already handled.
*/
public RunOutcome execute(WikiPipelineDefinitionEntity def, String triggerSubject,
String triggerBucket, String inputJson) {
if (def.getEnabled() != null && def.getEnabled() == 0) {
return new RunOutcome(null, false);
}
if (def.getOwnerAgentId() == null) {
throw new IllegalStateException("Pipeline definition " + def.getId() + " has no owner agent");
}
WikiPipelineRunEntity run = new WikiPipelineRunEntity();
run.setDefinitionId(def.getId());
run.setKbId(def.getKbId());
run.setStatus("running");
run.setTriggerType(def.getTriggerType());
run.setTriggerSubject(triggerSubject);
run.setTriggerBucket(triggerBucket);
run.setInputJson(inputJson);
run.setStartedAt(LocalDateTime.now());
run.setCreateTime(LocalDateTime.now());
try {
runMapper.insert(run);
} catch (DuplicateKeyException dup) {
// Another instance / earlier trigger already created this run.
log.info("[WikiPipeline] duplicate trigger for def={} subject={} bucket={} — skipped",
def.getId(), triggerSubject, triggerBucket);
return new RunOutcome(null, true);
}
List