Files
2026-08-16 16:19:23 +03:00

97 lines
3.7 KiB
Go

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}
}