package continuityexecutor import ( "bytes" "encoding/json" "fmt" "net/http" "net/url" "strings" commitcontract "git.cin.su/m/Assistant/contracts/commit" "git.cin.su/m/Assistant/contracts/continuity" "git.cin.su/m/Assistant/modules/commit/ports" ) type Adapter struct { baseURL string token string client *http.Client } func New(baseURL, token string, client *http.Client) (*Adapter, error) { baseURL = strings.TrimRight(baseURL, "/") if baseURL == "" || token == "" || client == nil { return nil, fmt.Errorf("continuity executor requires base URL, token, and HTTP client") } if _, err := url.ParseRequestURI(baseURL); err != nil { return nil, fmt.Errorf("invalid continuity executor URL: %w", err) } return &Adapter{baseURL: baseURL, token: token, client: client}, nil } func (a *Adapter) Execute(action commitcontract.AuthorizedAction) ports.ExecutionResult { if action.Proposal.CapabilityID != commitcontract.CapabilityContinuityTransition || action.Proposal.Operation != commitcontract.OperationSatisfyDelivered { return ports.ExecutionResult{Known: true, Succeeded: false} } command := continuity.SatisfyDeliveredCommand{ ActionIntentID: continuity.CommittedActionIntentID(action.Proposal.IntentID), AuthorizedActionID: continuity.CommittedAuthorizedActionID(action.ID), OwnerPrincipalID: action.PrincipalID, Subject: action.Proposal.ContinuitySubject, } body, err := json.Marshal(command) if err != nil { return ports.ExecutionResult{Known: true, Succeeded: false} } request, err := http.NewRequest(http.MethodPost, a.baseURL+"/v1/internal/executor/satisfy-delivered", bytes.NewReader(body)) if err != nil { return ports.ExecutionResult{Known: true, Succeeded: false} } request.Header.Set("Authorization", "Bearer "+a.token) request.Header.Set("Content-Type", "application/json") response, err := a.client.Do(request) if err != nil { return ports.ExecutionResult{Known: false} } defer response.Body.Close() if response.StatusCode == http.StatusOK { var receipt continuity.ActionReceipt if json.NewDecoder(response.Body).Decode(&receipt) != nil || receipt.ActionIntentID != command.ActionIntentID || receipt.AuthorizedActionID != command.AuthorizedActionID || receipt.ResultState != continuity.ProspectiveSatisfied { return ports.ExecutionResult{Known: false} } return ports.ExecutionResult{Known: true, Succeeded: true} } if response.StatusCode >= 400 && response.StatusCode < 500 { return ports.ExecutionResult{Known: true, Succeeded: false} } return ports.ExecutionResult{Known: false} } func (a *Adapter) Reconcile(action commitcontract.AuthorizedAction) ports.ExecutionResult { endpoint := a.baseURL + "/v1/internal/executor/receipts/" + url.PathEscape(string(action.Proposal.IntentID)) request, err := http.NewRequest(http.MethodGet, endpoint, nil) if err != nil { return ports.ExecutionResult{Known: false} } request.Header.Set("Authorization", "Bearer "+a.token) response, err := a.client.Do(request) if err != nil { return ports.ExecutionResult{Known: false} } defer response.Body.Close() if response.StatusCode != http.StatusOK { return ports.ExecutionResult{Known: false} } var receipt continuity.ActionReceipt if json.NewDecoder(response.Body).Decode(&receipt) != nil || receipt.ActionIntentID != continuity.CommittedActionIntentID(action.Proposal.IntentID) || receipt.AuthorizedActionID != continuity.CommittedAuthorizedActionID(action.ID) || receipt.OwnerPrincipalID != action.PrincipalID || receipt.Subject != action.Proposal.ContinuitySubject { return ports.ExecutionResult{Known: false} } return ports.ExecutionResult{Known: true, Succeeded: receipt.ResultState == continuity.ProspectiveSatisfied} }