Files
m 5d4e064a94
CI / test (push) Successful in 16s
CI / test (pull_request) Successful in 15s
DEV-000065 assistant presence attention surface
2026-08-17 16:23:16 +03:00

117 lines
6.0 KiB
Go

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
}