|
|
|
@@ -0,0 +1,78 @@
|
|
|
|
|
package domain
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"errors"
|
|
|
|
|
|
|
|
|
|
"git.cin.su/m/Assistant/contracts/authority"
|
|
|
|
|
"git.cin.su/m/Assistant/contracts/capability"
|
|
|
|
|
commitcontract "git.cin.su/m/Assistant/contracts/commit"
|
|
|
|
|
"git.cin.su/m/Assistant/modules/commit/ports"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
ErrInvalidProposal = errors.New("action proposal lacks target, revision, capability, or operation")
|
|
|
|
|
ErrUnknownOutcome = errors.New("unknown outcome requires reconciliation before retry")
|
|
|
|
|
ErrInvalidReconciliation = errors.New("reconciliation requires the matching UNKNOWN_OUTCOME attempt and a new attempt identity")
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type AuthorizationContext = authority.AuthorizationContext
|
|
|
|
|
|
|
|
|
|
// Reconcile verifies an UNKNOWN_OUTCOME through a read-only external query.
|
|
|
|
|
// It never invokes Executor.Execute and therefore cannot repeat the effect.
|
|
|
|
|
func Reconcile(action commitcontract.AuthorizedAction, previous commitcontract.ActionAttempt, attemptID commitcontract.ActionAttemptID, reconciler ports.Reconciler) (commitcontract.ActionAttempt, error) {
|
|
|
|
|
if action.State != commitcontract.ActionAuthorized || previous.State != commitcontract.ActionUnknownOutcome ||
|
|
|
|
|
previous.AuthorizedID != action.ID || previous.IntentID != action.Proposal.IntentID || attemptID == "" || attemptID == previous.ID {
|
|
|
|
|
return commitcontract.ActionAttempt{}, ErrInvalidReconciliation
|
|
|
|
|
}
|
|
|
|
|
attempt := commitcontract.ActionAttempt{ID: attemptID, AuthorizedID: action.ID, IntentID: action.Proposal.IntentID, State: commitcontract.ActionExecuting}
|
|
|
|
|
result := reconciler.Reconcile(action)
|
|
|
|
|
if !result.Known {
|
|
|
|
|
attempt.State = commitcontract.ActionUnknownOutcome
|
|
|
|
|
return attempt, ErrUnknownOutcome
|
|
|
|
|
}
|
|
|
|
|
if result.Succeeded {
|
|
|
|
|
attempt.State = commitcontract.ActionSucceeded
|
|
|
|
|
return attempt, nil
|
|
|
|
|
}
|
|
|
|
|
attempt.State = commitcontract.ActionFailed
|
|
|
|
|
return attempt, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Authorize is the Commit boundary. A proposal alone never reaches Executor.
|
|
|
|
|
func Authorize(proposal commitcontract.ActionProposal, context AuthorizationContext, validator ports.AuthorityValidator, authorizedID commitcontract.AuthorizedActionID) (commitcontract.AuthorizedAction, error) {
|
|
|
|
|
if proposal.IntentID == "" || proposal.ActorID == "" || proposal.CapabilityID == capability.CapabilityID("") ||
|
|
|
|
|
proposal.Target.TaskID == "" || proposal.Target.GoalID == "" || proposal.Target.ScopeID == "" || proposal.Target.Revision == 0 || proposal.Operation == "" {
|
|
|
|
|
return commitcontract.AuthorizedAction{}, ErrInvalidProposal
|
|
|
|
|
}
|
|
|
|
|
if proposal.ActorID != context.Binding.ActorID || proposal.Target.ScopeID != context.Scope.ID {
|
|
|
|
|
return commitcontract.AuthorizedAction{}, ErrInvalidProposal
|
|
|
|
|
}
|
|
|
|
|
if err := validator.Validate(context); err != nil {
|
|
|
|
|
return commitcontract.AuthorizedAction{}, err
|
|
|
|
|
}
|
|
|
|
|
if authorizedID == "" {
|
|
|
|
|
return commitcontract.AuthorizedAction{}, ErrInvalidProposal
|
|
|
|
|
}
|
|
|
|
|
proposal.State = commitcontract.ActionProposed
|
|
|
|
|
return commitcontract.AuthorizedAction{ID: authorizedID, Proposal: proposal, GrantID: context.Grant.ID, ScopeID: context.Scope.ID, State: commitcontract.ActionAuthorized}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Execute records UNKNOWN_OUTCOME rather than silently retrying an ambiguous
|
|
|
|
|
// external mutation.
|
|
|
|
|
func Execute(action commitcontract.AuthorizedAction, attemptID commitcontract.ActionAttemptID, executor ports.Executor) (commitcontract.ActionAttempt, error) {
|
|
|
|
|
if action.State != commitcontract.ActionAuthorized || attemptID == "" {
|
|
|
|
|
return commitcontract.ActionAttempt{}, ErrUnknownOutcome
|
|
|
|
|
}
|
|
|
|
|
attempt := commitcontract.ActionAttempt{ID: attemptID, AuthorizedID: action.ID, IntentID: action.Proposal.IntentID, State: commitcontract.ActionExecuting}
|
|
|
|
|
result := executor.Execute(action)
|
|
|
|
|
if !result.Known {
|
|
|
|
|
attempt.State = commitcontract.ActionUnknownOutcome
|
|
|
|
|
return attempt, ErrUnknownOutcome
|
|
|
|
|
}
|
|
|
|
|
if result.Succeeded {
|
|
|
|
|
attempt.State = commitcontract.ActionSucceeded
|
|
|
|
|
return attempt, nil
|
|
|
|
|
}
|
|
|
|
|
attempt.State = commitcontract.ActionFailed
|
|
|
|
|
return attempt, nil
|
|
|
|
|
}
|