Version: 0.9.70.dev.260504
后端:
1. 阶段 5 schedule 首刀服务化落地,新增 `cmd/schedule`、`services/schedule/{dao,rpc,sv,core}`、`gateway/client/schedule`、`shared/contracts/schedule` 和 schedule port
2. gateway `/api/v1/schedule/*` 切到 schedule zrpc client,HTTP 门面只保留鉴权、参数绑定、超时和轻量转发
3. active-scheduler 的 schedule facts、feedback 和 confirm apply 改为调用 schedule RPC adapter,减少对 `schedule_events`、`schedules`、`task_classes`、`task_items` 的跨域 DB 依赖
4. 单体聊天主动调度 rerun 的 schedule 读写链路切到 schedule RPC,迁移期仅保留 task facts 直读 Gorm
5. 为 schedule zrpc 补充 `Ping` 启动健康检查,并在 gateway client 与 active-scheduler adapter 初始化时校验服务可用
6. `cmd/schedule` 独立初始化 DB / Redis,只 AutoMigrate schedule 自有表,并显式检查迁移期 task / task-class 依赖表
7. 更新 active-scheduler 依赖表检查和 preview confirm apply 抽象,保留旧 Gorm 实现作为迁移期回退路径
8. 补充 `schedule.rpc` 示例配置和 schedule HTTP RPC 超时配置
文档:
1. 更新微服务迁移计划,将阶段 5 schedule 首刀进展、当前切流点、旧实现保留范围和 active-scheduler DB 依赖收缩情况写入基线
This commit is contained in:
327
backend/services/active_scheduler/core/adapters/schedule_rpc.go
Normal file
327
backend/services/active_scheduler/core/adapters/schedule_rpc.go
Normal file
@@ -0,0 +1,327 @@
|
||||
package adapters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
activeapplyadapter "github.com/LoveLosita/smartflow/backend/services/active_scheduler/core/applyadapter"
|
||||
activeports "github.com/LoveLosita/smartflow/backend/services/active_scheduler/core/ports"
|
||||
schedulepb "github.com/LoveLosita/smartflow/backend/services/schedule/rpc/pb"
|
||||
schedulecontracts "github.com/LoveLosita/smartflow/backend/shared/contracts/schedule"
|
||||
"github.com/zeromicro/go-zero/zrpc"
|
||||
"google.golang.org/genproto/googleapis/rpc/errdetails"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultScheduleRPCEndpoint = "127.0.0.1:9084"
|
||||
defaultScheduleRPCTimeout = 6 * time.Second
|
||||
scheduleApplyErrorDomain = "smartflow.schedule.apply"
|
||||
)
|
||||
|
||||
type ScheduleRPCConfig struct {
|
||||
Endpoints []string
|
||||
Target string
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// ScheduleRPCAdapter 是 active-scheduler 访问 schedule 服务的 RPC 适配器。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 只把 active-scheduler 内部端口 DTO 与 shared/contracts/schedule 互转;
|
||||
// 2. 不读取数据库、不实现 schedule 写入状态机;
|
||||
// 3. 让 active-scheduler 不再直接访问 schedule_events / schedules。
|
||||
type ScheduleRPCAdapter struct {
|
||||
rpc schedulepb.ScheduleClient
|
||||
}
|
||||
|
||||
func NewScheduleRPCAdapter(cfg ScheduleRPCConfig) (*ScheduleRPCAdapter, error) {
|
||||
timeout := cfg.Timeout
|
||||
if timeout <= 0 {
|
||||
timeout = defaultScheduleRPCTimeout
|
||||
}
|
||||
endpoints := normalizeScheduleRPCEndpoints(cfg.Endpoints)
|
||||
target := strings.TrimSpace(cfg.Target)
|
||||
if len(endpoints) == 0 && target == "" {
|
||||
endpoints = []string{defaultScheduleRPCEndpoint}
|
||||
}
|
||||
zclient, err := zrpc.NewClient(zrpc.RpcClientConf{
|
||||
Endpoints: endpoints,
|
||||
Target: target,
|
||||
NonBlock: true,
|
||||
Timeout: int64(timeout / time.Millisecond),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
adapter := &ScheduleRPCAdapter{rpc: schedulepb.NewScheduleClient(zclient.Conn())}
|
||||
if err := adapter.ping(timeout); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return adapter, nil
|
||||
}
|
||||
|
||||
func ReadersWithScheduleRPC(taskReader activeports.TaskReader, scheduleReader *ScheduleRPCAdapter) activeports.Readers {
|
||||
return activeports.Readers{
|
||||
TaskReader: taskReader,
|
||||
ScheduleReader: scheduleReader,
|
||||
FeedbackReader: scheduleReader,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *ScheduleRPCAdapter) GetScheduleFactsByWindow(ctx context.Context, req activeports.ScheduleWindowRequest) (activeports.ScheduleWindowFacts, error) {
|
||||
if err := a.ensureReady(); err != nil {
|
||||
return activeports.ScheduleWindowFacts{}, err
|
||||
}
|
||||
payload, err := json.Marshal(toScheduleWindowContract(req))
|
||||
if err != nil {
|
||||
return activeports.ScheduleWindowFacts{}, err
|
||||
}
|
||||
resp, err := a.rpc.GetScheduleFactsByWindow(ctx, &schedulepb.JSONRequest{PayloadJson: payload})
|
||||
if err != nil {
|
||||
return activeports.ScheduleWindowFacts{}, scheduleRPCError(err)
|
||||
}
|
||||
var facts schedulecontracts.ScheduleWindowFacts
|
||||
if err := json.Unmarshal(jsonBytes(resp), &facts); err != nil {
|
||||
return activeports.ScheduleWindowFacts{}, err
|
||||
}
|
||||
return scheduleFactsToActive(facts), nil
|
||||
}
|
||||
|
||||
func (a *ScheduleRPCAdapter) GetFeedbackSignal(ctx context.Context, req activeports.FeedbackRequest) (activeports.FeedbackFact, bool, error) {
|
||||
if err := a.ensureReady(); err != nil {
|
||||
return activeports.FeedbackFact{}, false, err
|
||||
}
|
||||
payload, err := json.Marshal(schedulecontracts.FeedbackRequest{
|
||||
UserID: req.UserID,
|
||||
FeedbackID: req.FeedbackID,
|
||||
IdempotencyKey: req.IdempotencyKey,
|
||||
TargetType: req.TargetType,
|
||||
TargetID: req.TargetID,
|
||||
})
|
||||
if err != nil {
|
||||
return activeports.FeedbackFact{}, false, err
|
||||
}
|
||||
resp, err := a.rpc.GetFeedbackSignal(ctx, &schedulepb.JSONRequest{PayloadJson: payload})
|
||||
if err != nil {
|
||||
return activeports.FeedbackFact{}, false, scheduleRPCError(err)
|
||||
}
|
||||
var contractResp schedulecontracts.FeedbackResponse
|
||||
if err := json.Unmarshal(jsonBytes(resp), &contractResp); err != nil {
|
||||
return activeports.FeedbackFact{}, false, err
|
||||
}
|
||||
return feedbackFactToActive(contractResp.Feedback), contractResp.Found, nil
|
||||
}
|
||||
|
||||
func (a *ScheduleRPCAdapter) ApplyActiveScheduleChanges(ctx context.Context, req activeapplyadapter.ApplyActiveScheduleRequest) (activeapplyadapter.ApplyActiveScheduleResult, error) {
|
||||
if err := a.ensureReady(); err != nil {
|
||||
return activeapplyadapter.ApplyActiveScheduleResult{}, err
|
||||
}
|
||||
payload, err := json.Marshal(toScheduleApplyContract(req))
|
||||
if err != nil {
|
||||
return activeapplyadapter.ApplyActiveScheduleResult{}, err
|
||||
}
|
||||
resp, err := a.rpc.ApplyActiveScheduleChanges(ctx, &schedulepb.JSONRequest{PayloadJson: payload})
|
||||
if err != nil {
|
||||
return activeapplyadapter.ApplyActiveScheduleResult{}, scheduleRPCError(err)
|
||||
}
|
||||
var result schedulecontracts.ApplyActiveScheduleResult
|
||||
if err := json.Unmarshal(jsonBytes(resp), &result); err != nil {
|
||||
return activeapplyadapter.ApplyActiveScheduleResult{}, err
|
||||
}
|
||||
return activeapplyadapter.ApplyActiveScheduleResult{
|
||||
ApplyID: result.ApplyID,
|
||||
AppliedEventIDs: result.AppliedEventIDs,
|
||||
AppliedScheduleIDs: result.AppliedScheduleIDs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *ScheduleRPCAdapter) ensureReady() error {
|
||||
if a == nil || a.rpc == nil {
|
||||
return errors.New("schedule rpc adapter 未初始化")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ScheduleRPCAdapter) ping(timeout time.Duration) error {
|
||||
if err := a.ensureReady(); err != nil {
|
||||
return err
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
_, err := a.rpc.Ping(ctx, &schedulepb.StatusResponse{})
|
||||
return scheduleRPCError(err)
|
||||
}
|
||||
|
||||
func scheduleRPCError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
st, ok := status.FromError(err)
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
for _, detail := range st.Details() {
|
||||
info, ok := detail.(*errdetails.ErrorInfo)
|
||||
if !ok || info.Domain != scheduleApplyErrorDomain {
|
||||
continue
|
||||
}
|
||||
message := strings.TrimSpace(st.Message())
|
||||
if message == "" && info.Metadata != nil {
|
||||
message = strings.TrimSpace(info.Metadata["info"])
|
||||
}
|
||||
return &activeapplyadapter.ApplyError{
|
||||
Code: strings.TrimSpace(info.Reason),
|
||||
Message: message,
|
||||
Cause: err,
|
||||
}
|
||||
}
|
||||
if st.Code() == codes.Internal || st.Code() == codes.Unavailable || st.Code() == codes.DeadlineExceeded {
|
||||
return fmt.Errorf("调用 schedule zrpc 服务失败: %w", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func toScheduleWindowContract(req activeports.ScheduleWindowRequest) schedulecontracts.ScheduleWindowRequest {
|
||||
return schedulecontracts.ScheduleWindowRequest{
|
||||
UserID: req.UserID,
|
||||
TargetType: req.TargetType,
|
||||
TargetID: req.TargetID,
|
||||
WindowStart: req.WindowStart,
|
||||
WindowEnd: req.WindowEnd,
|
||||
Now: req.Now,
|
||||
}
|
||||
}
|
||||
|
||||
func scheduleFactsToActive(facts schedulecontracts.ScheduleWindowFacts) activeports.ScheduleWindowFacts {
|
||||
events := make([]activeports.ScheduleEventFact, 0, len(facts.Events))
|
||||
for _, event := range facts.Events {
|
||||
events = append(events, scheduleEventFactToActive(event))
|
||||
}
|
||||
return activeports.ScheduleWindowFacts{
|
||||
Events: events,
|
||||
OccupiedSlots: scheduleSlotsToActive(facts.OccupiedSlots),
|
||||
FreeSlots: scheduleSlotsToActive(facts.FreeSlots),
|
||||
NextDynamicTask: scheduleEventFactPtrToActive(facts.NextDynamicTask),
|
||||
TargetAlreadyScheduled: facts.TargetAlreadyScheduled,
|
||||
}
|
||||
}
|
||||
|
||||
func scheduleEventFactToActive(event schedulecontracts.ScheduleEventFact) activeports.ScheduleEventFact {
|
||||
return activeports.ScheduleEventFact{
|
||||
ID: event.ID,
|
||||
UserID: event.UserID,
|
||||
Title: event.Title,
|
||||
SourceType: event.SourceType,
|
||||
RelID: event.RelID,
|
||||
IsDynamicTask: event.IsDynamicTask,
|
||||
IsCompleted: event.IsCompleted,
|
||||
Slots: scheduleSlotsToActive(event.Slots),
|
||||
TaskClassID: event.TaskClassID,
|
||||
TaskItemID: event.TaskItemID,
|
||||
CanBeShortened: event.CanBeShortened,
|
||||
}
|
||||
}
|
||||
|
||||
func scheduleEventFactPtrToActive(event *schedulecontracts.ScheduleEventFact) *activeports.ScheduleEventFact {
|
||||
if event == nil {
|
||||
return nil
|
||||
}
|
||||
converted := scheduleEventFactToActive(*event)
|
||||
return &converted
|
||||
}
|
||||
|
||||
func scheduleSlotsToActive(slots []schedulecontracts.Slot) []activeports.Slot {
|
||||
out := make([]activeports.Slot, 0, len(slots))
|
||||
for _, slot := range slots {
|
||||
out = append(out, activeports.Slot{
|
||||
Week: slot.Week,
|
||||
DayOfWeek: slot.DayOfWeek,
|
||||
Section: slot.Section,
|
||||
StartAt: slot.StartAt,
|
||||
EndAt: slot.EndAt,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func feedbackFactToActive(feedback schedulecontracts.FeedbackFact) activeports.FeedbackFact {
|
||||
return activeports.FeedbackFact{
|
||||
FeedbackID: feedback.FeedbackID,
|
||||
Text: feedback.Text,
|
||||
TargetKnown: feedback.TargetKnown,
|
||||
TargetEventID: feedback.TargetEventID,
|
||||
TargetTaskItemID: feedback.TargetTaskItemID,
|
||||
TargetTitle: feedback.TargetTitle,
|
||||
SubmittedAt: feedback.SubmittedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func toScheduleApplyContract(req activeapplyadapter.ApplyActiveScheduleRequest) schedulecontracts.ApplyActiveScheduleRequest {
|
||||
changes := make([]schedulecontracts.ApplyChange, 0, len(req.Changes))
|
||||
for _, change := range req.Changes {
|
||||
changes = append(changes, schedulecontracts.ApplyChange{
|
||||
ChangeID: change.ChangeID,
|
||||
ChangeType: change.ChangeType,
|
||||
TargetType: change.TargetType,
|
||||
TargetID: change.TargetID,
|
||||
ToSlot: toScheduleSlotSpan(change.ToSlot),
|
||||
DurationSections: change.DurationSections,
|
||||
Metadata: cloneStringMap(change.Metadata),
|
||||
})
|
||||
}
|
||||
return schedulecontracts.ApplyActiveScheduleRequest{
|
||||
PreviewID: req.PreviewID,
|
||||
ApplyID: req.ApplyID,
|
||||
UserID: req.UserID,
|
||||
CandidateID: req.CandidateID,
|
||||
Changes: changes,
|
||||
RequestedAt: req.RequestedAt,
|
||||
TraceID: req.TraceID,
|
||||
}
|
||||
}
|
||||
|
||||
func toScheduleSlotSpan(span *activeapplyadapter.SlotSpan) *schedulecontracts.SlotSpan {
|
||||
if span == nil {
|
||||
return nil
|
||||
}
|
||||
return &schedulecontracts.SlotSpan{
|
||||
Start: schedulecontracts.Slot{Week: span.Start.Week, DayOfWeek: span.Start.DayOfWeek, Section: span.Start.Section},
|
||||
End: schedulecontracts.Slot{Week: span.End.Week, DayOfWeek: span.End.DayOfWeek, Section: span.End.Section},
|
||||
DurationSections: span.DurationSections,
|
||||
}
|
||||
}
|
||||
|
||||
func jsonBytes(resp *schedulepb.JSONResponse) []byte {
|
||||
if resp == nil || len(resp.DataJson) == 0 {
|
||||
return []byte("null")
|
||||
}
|
||||
return resp.DataJson
|
||||
}
|
||||
|
||||
func normalizeScheduleRPCEndpoints(values []string) []string {
|
||||
endpoints := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
trimmed := strings.TrimSpace(value)
|
||||
if trimmed != "" {
|
||||
endpoints = append(endpoints, trimmed)
|
||||
}
|
||||
}
|
||||
return endpoints
|
||||
}
|
||||
|
||||
func cloneStringMap(input map[string]string) map[string]string {
|
||||
if len(input) == 0 {
|
||||
return nil
|
||||
}
|
||||
output := make(map[string]string, len(input))
|
||||
for key, value := range input {
|
||||
output[key] = value
|
||||
}
|
||||
return output
|
||||
}
|
||||
@@ -24,11 +24,15 @@ type PreviewConfirmService struct {
|
||||
dryRun *DryRunService
|
||||
preview *activepreview.Service
|
||||
activeDAO *dao.ActiveScheduleDAO
|
||||
applyAdapter *applyadapter.GormApplyAdapter
|
||||
applyAdapter scheduleApplyAdapter
|
||||
clock func() time.Time
|
||||
}
|
||||
|
||||
func NewPreviewConfirmService(dryRun *DryRunService, previewService *activepreview.Service, activeDAO *dao.ActiveScheduleDAO, applyAdapter *applyadapter.GormApplyAdapter) (*PreviewConfirmService, error) {
|
||||
type scheduleApplyAdapter interface {
|
||||
ApplyActiveScheduleChanges(ctx context.Context, req applyadapter.ApplyActiveScheduleRequest) (applyadapter.ApplyActiveScheduleResult, error)
|
||||
}
|
||||
|
||||
func NewPreviewConfirmService(dryRun *DryRunService, previewService *activepreview.Service, activeDAO *dao.ActiveScheduleDAO, applyAdapter scheduleApplyAdapter) (*PreviewConfirmService, error) {
|
||||
if dryRun == nil {
|
||||
return nil, errors.New("dry-run service 不能为空")
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ type runtimeDependencyTable struct {
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 只检查表是否存在,不 AutoMigrate、不补列、不修改任何跨域表;
|
||||
// 2. 把 active-scheduler 运行时仍然需要的 task / schedule / agent / notification outbox 边界显式化;
|
||||
// 2. 把 active-scheduler 运行时仍然需要的 task / agent / notification outbox 边界显式化;
|
||||
// 3. 若部署顺序、库权限或表结构归属不满足,启动阶段直接 fail fast,避免第一次 trigger 才反复重试。
|
||||
func ensureRuntimeDependencyTables(db *gorm.DB) error {
|
||||
if db == nil {
|
||||
@@ -110,7 +110,7 @@ func ensureTableExists(db *gorm.DB, table runtimeDependencyTable) error {
|
||||
// 说明:
|
||||
// 1. active-scheduler 自有表在 OpenDBFromConfig 内迁移,这里只放跨域依赖;
|
||||
// 2. notification outbox 表名来自 service catalog,避免和 outbox 多表路由配置漂移;
|
||||
// 3. 后续切到 task/schedule/agent/notification RPC 或 read model 后,应从这里移除对应表依赖。
|
||||
// 3. schedule 读写已切到 schedule RPC;后续切到 task/agent/notification RPC 或 read model 后,应继续移除对应表依赖。
|
||||
func activeSchedulerRuntimeDependencyTables() []runtimeDependencyTable {
|
||||
notificationOutboxTable := "notification_outbox_messages"
|
||||
if cfg, ok := outboxinfra.ResolveServiceConfig(outboxinfra.ServiceNotification); ok && cfg.TableName != "" {
|
||||
@@ -118,11 +118,7 @@ func activeSchedulerRuntimeDependencyTables() []runtimeDependencyTable {
|
||||
}
|
||||
|
||||
return []runtimeDependencyTable{
|
||||
{Name: "tasks", Reason: "dry-run 读取 task_pool 事实,confirm 时锁定 task_pool 目标"},
|
||||
{Name: "schedule_events", Reason: "dry-run 读取日程事实,confirm 时写入正式日程事件"},
|
||||
{Name: "schedules", Reason: "dry-run 读取节次占用,confirm 时写入正式节次"},
|
||||
{Name: "task_classes", Reason: "confirm create_makeup 时校验 task_item 归属"},
|
||||
{Name: "task_items", Reason: "confirm create_makeup 时锁定 task_item 目标"},
|
||||
{Name: "tasks", Reason: "迁移期 dry-run / due job scanner 仍读取 task_pool 事实,下一轮切 task RPC 后移除"},
|
||||
{Name: "agent_chats", Reason: "trigger 生成 preview 后预建主动调度会话"},
|
||||
{Name: "chat_histories", Reason: "trigger 生成 preview 后写入会话首屏消息"},
|
||||
{Name: "agent_timeline_events", Reason: "trigger 生成 preview 后写入主动调度时间线卡片"},
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
eventsvc "github.com/LoveLosita/smartflow/backend/service/events"
|
||||
activeadapters "github.com/LoveLosita/smartflow/backend/services/active_scheduler/core/adapters"
|
||||
activeapply "github.com/LoveLosita/smartflow/backend/services/active_scheduler/core/apply"
|
||||
"github.com/LoveLosita/smartflow/backend/services/active_scheduler/core/applyadapter"
|
||||
activeapplyadapter "github.com/LoveLosita/smartflow/backend/services/active_scheduler/core/applyadapter"
|
||||
activegraph "github.com/LoveLosita/smartflow/backend/services/active_scheduler/core/graph"
|
||||
activejob "github.com/LoveLosita/smartflow/backend/services/active_scheduler/core/job"
|
||||
activepreview "github.com/LoveLosita/smartflow/backend/services/active_scheduler/core/preview"
|
||||
@@ -39,6 +39,7 @@ type Options struct {
|
||||
JobScanEvery time.Duration
|
||||
JobScanLimit int
|
||||
KafkaConfig kafkabus.Config
|
||||
ScheduleRPC activeadapters.ScheduleRPCConfig
|
||||
}
|
||||
|
||||
// Service 是 active-scheduler 独立进程内的服务门面。
|
||||
@@ -69,12 +70,16 @@ func New(db *gorm.DB, llmService *llmservice.Service, opts Options) (*Service, e
|
||||
|
||||
activeDAO := rootdao.NewActiveScheduleDAO(db)
|
||||
activeReaders := activeadapters.NewGormReaders(db)
|
||||
readers := activeadapters.ReadersFromGorm(activeReaders)
|
||||
scheduleRPCAdapter, err := activeadapters.NewScheduleRPCAdapter(opts.ScheduleRPC)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("initialize schedule rpc adapter failed: %w", err)
|
||||
}
|
||||
readers := activeadapters.ReadersWithScheduleRPC(activeReaders, scheduleRPCAdapter)
|
||||
dryRun, err := activesvc.NewDryRunService(readers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
previewConfirm, err := buildPreviewConfirmService(db, activeDAO, dryRun)
|
||||
previewConfirm, err := buildPreviewConfirmService(activeDAO, dryRun, scheduleRPCAdapter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -259,12 +264,14 @@ func (s *Service) ConfirmPreview(ctx context.Context, req contracts.ConfirmPrevi
|
||||
return marshalResponseJSON(result)
|
||||
}
|
||||
|
||||
func buildPreviewConfirmService(db *gorm.DB, activeDAO *rootdao.ActiveScheduleDAO, dryRun *activesvc.DryRunService) (*activesvc.PreviewConfirmService, error) {
|
||||
func buildPreviewConfirmService(activeDAO *rootdao.ActiveScheduleDAO, dryRun *activesvc.DryRunService, scheduleApplyAdapter interface {
|
||||
ApplyActiveScheduleChanges(context.Context, activeapplyadapter.ApplyActiveScheduleRequest) (activeapplyadapter.ApplyActiveScheduleResult, error)
|
||||
}) (*activesvc.PreviewConfirmService, error) {
|
||||
previewService, err := activepreview.NewService(activeDAO)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return activesvc.NewPreviewConfirmService(dryRun, previewService, activeDAO, applyadapter.NewGormApplyAdapter(db))
|
||||
return activesvc.NewPreviewConfirmService(dryRun, previewService, activeDAO, scheduleApplyAdapter)
|
||||
}
|
||||
|
||||
func buildGraphRunner(dryRun *activesvc.DryRunService, llmService *llmservice.Service) (*activegraph.Runner, error) {
|
||||
|
||||
Reference in New Issue
Block a user