package domain import ( "encoding/hex" "errors" "strings" "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/contracts/continuity" "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, authorizedAtUnixMillis int64) (commitcontract.AuthorizedAction, error) { if proposal.IntentID == "" || proposal.ActorID == "" || proposal.CapabilityID == capability.CapabilityID("") || proposal.Operation == "" || authorizedAtUnixMillis <= 0 { return commitcontract.AuthorizedAction{}, ErrInvalidProposal } if proposal.ActorID != context.Binding.ActorID { return commitcontract.AuthorizedAction{}, ErrInvalidProposal } if proposal.Operation == commitcontract.OperationDeliverProspectiveNotification { if proposal.Target.ScopeID != "" && proposal.Target.ScopeID != context.Scope.ID { return commitcontract.AuthorizedAction{}, ErrInvalidProposal } } else if proposal.Target.TaskID == "" || proposal.Target.GoalID == "" || proposal.Target.ScopeID == "" || proposal.Target.Revision == 0 || proposal.Target.ScopeID != context.Scope.ID { return commitcontract.AuthorizedAction{}, ErrInvalidProposal } if !allowedOperation(proposal, context) { 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, PrincipalID: context.Binding.PrincipalID, GrantID: context.Grant.ID, GrantRevision: context.Grant.Revision, ScopeID: context.Scope.ID, AuthorizedAtUnixMillis: authorizedAtUnixMillis, State: commitcontract.ActionAuthorized}, nil } func allowedOperation(proposal commitcontract.ActionProposal, context AuthorizationContext) bool { switch { case proposal.CapabilityID == commitcontract.CapabilityTestExecutor && proposal.Operation == commitcontract.OperationTestIdempotentEffect: return true case context.Grant.Class == authority.GrantClassCreator && proposal.CapabilityID == commitcontract.CapabilityContinuityTransition && proposal.Operation == commitcontract.OperationSatisfyDelivered: subject := proposal.ContinuitySubject return subject.ProspectiveItemID != "" && subject.ExpectedRevision > 0 && subject.FromState == continuity.ProspectiveDelivered && subject.ToState == continuity.ProspectiveSatisfied && strings.TrimSpace(subject.EvidenceRef) != "" case context.Grant.Class == authority.GrantClassCreator && proposal.CapabilityID == commitcontract.CapabilityProspectiveNotification && proposal.Operation == commitcontract.OperationDeliverProspectiveNotification: subject := proposal.NotificationSubject return subject.ProspectiveItemID != "" && subject.ExpectedRevision > 0 && subject.TemplateCode == commitcontract.ProspectiveDeliveredTemplateV1 && subject.BindingID != "" && subject.BindingRevision > 0 && strings.TrimSpace(subject.RenderedTitle) != "" && len(subject.RenderedTitle) <= 160 && strings.TrimSpace(subject.RenderedMessage) != "" && len(subject.RenderedMessage) <= 2048 && validDigest(subject.RequestDigest) default: return false } } func validDigest(value string) bool { decoded, err := hex.DecodeString(value) return err == nil && len(decoded) == 32 && value == strings.ToLower(value) } // 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 }