package intentflow import ( "context" "chat-agent-system/internal/app/uow" ) // DispatchEligible is the production step dispatcher. It reuses ClaimStep, // ExecuteClaimed, and RecoverReceipt on the existing intent_plan_steps ledger. // It must not introduce a second decision queue or step state machine. // // After a crash between domain receipt and plan write-back, RecoverReceipt // replays the owner receipt without re-executing the side effect. func (p *Plans) DispatchEligible(ctx context.Context, workerID, planID string) (*PlanView, error) { if workerID == "" || planID == "" { return nil, ErrPlanDependencyInvalid } var prin Principal var ids []string err := p.withTx(ctx, func(ctx context.Context, scope uow.TxScope) error { plan, err := p.store.GetPlan(ctx, scope, planID) if err != nil { return err } if plan == nil { return ErrPlanNotFoundOrHidden } prin = plan.Principal for _, s := range plan.Steps { if s.Status == StepEligible || s.Status == StepPending || s.Status == StepRunning { ids = append(ids, s.StepID) } } return nil }) if err != nil { return nil, err } var last *PlanView var firstErr error for _, id := range ids { claimed, claimErr := p.ClaimStep(ctx, workerID, planID, id) if claimErr != nil { if firstErr == nil { firstErr = claimErr } continue } if claimed == nil { continue } got, execErr := p.ExecuteClaimed(ctx, claimed) if execErr != nil { recovered, recErr := p.RecoverReceipt(ctx, claimed) if recErr == nil && recovered != nil { last = recovered continue } if firstErr == nil { firstErr = execErr } continue } last = got } if last == nil { view, getErr := p.GetPlan(ctx, prin, planID) if firstErr != nil { return view, firstErr } return view, getErr } return last, firstErr }