Replace FakeActionOwner with QueryOwner+QueryGuard at the composition root; DispatchEligible reuses claim/execute/recover so confirm stays zero-write before dispatch and default tier remains legacy.
76 lines
1.8 KiB
Go
76 lines
1.8 KiB
Go
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
|
|
}
|