Files
cmroubao/backend-api/internal/usecase/execution_result_service.go
T

818 lines
24 KiB
Go

package usecase
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"io"
"net/url"
"regexp"
"strconv"
"strings"
"time"
"unicode/utf8"
"cmroubao/backend-api/internal/domain"
)
const (
executionResultEventsOperation = "EVENTS"
executionResultEvidenceOperation = "EVIDENCE"
executionResultCandidatesOperation = "CANDIDATES"
executionResultCompleteOperation = "COMPLETE"
executionResultFailOperation = "FAIL"
manualFirstMode = "MANUAL_FIRST"
aiAssistedMode = "AI_ASSISTED"
)
type ExecutionResultService struct {
repository ExecutionResultRepository
store ReferenceImageStore
clock Clock
ids IDGenerator
}
type ExecutionResultIdentity struct {
UserID string
DeviceID string
TaskID string
ExecutionID string
ClaimGeneration int64
ClaimToken string
IdempotencyKey string
}
type ClientExecutionEvent struct {
ID string `json:"event_id"`
Step string `json:"step"`
Type string `json:"type"`
Message string `json:"message"`
OccurredAt string `json:"occurred_at"`
}
type AppendExecutionEventsCommand struct {
Identity ExecutionResultIdentity
Events []ClientExecutionEvent
}
type UploadExecutionEvidenceCommand struct {
Identity ExecutionResultIdentity
DeclaredMediaType string
Content io.Reader
}
type ExecutionProvenance struct {
ProviderID string `json:"provider_id"`
Model string `json:"model"`
PromptVersion string `json:"prompt_version"`
SchemaVersion int `json:"schema_version"`
}
type CandidateEvaluation struct {
Decision string `json:"decision"`
Score float64 `json:"score"`
Matched []string `json:"matched"`
MissingOrUncertain []string `json:"missing_or_uncertain"`
RejectionReasons []string `json:"rejection_reasons"`
Confidence float64 `json:"confidence"`
HardConstraints []CandidateHardConstraintEvaluation `json:"hard_constraints"`
}
type CandidateHardConstraintEvaluation struct {
Kind string `json:"kind"`
Expected string `json:"expected"`
Status string `json:"status"`
Evidence string `json:"evidence"`
}
type ExecutionCandidate struct {
Ordinal int `json:"ordinal"`
Title string `json:"title"`
SKUText string `json:"sku_text"`
Price string `json:"price"`
ProductURL string `json:"product_url"`
ImageURL string `json:"image_url"`
EvidenceAssetIDs []string `json:"evidence_asset_ids"`
Evaluation *CandidateEvaluation `json:"evaluation"`
}
type CandidateRecommendation struct {
CandidateOrdinal int `json:"candidate_ordinal"`
PolicyVersion string `json:"policy_version"`
Reasons []string `json:"reasons"`
}
type StoreExecutionCandidatesCommand struct {
Identity ExecutionResultIdentity
TaskContentSHA256 string
ExecutionMode string
SearchQuery string
Provenance *ExecutionProvenance
Candidates []ExecutionCandidate
Recommendation *CandidateRecommendation
}
type CompleteExecutionCommand struct {
Identity ExecutionResultIdentity
TaskContentSHA256 string
ExecutionMode string
Outcome string
OperatorReason string
Candidate *ExecutionCandidate
OrderSubmitted bool
}
type FailExecutionCommand struct {
Identity ExecutionResultIdentity
ErrorCode string
ErrorMessage string
ErrorStep string
Retryable bool
EvidenceAssetIDs []string
}
type UploadExecutionEvidenceResult struct {
Evidence domain.ExecutionEvidenceAsset
Replayed bool
}
type ExecutionEvidenceContent struct {
Evidence domain.ExecutionEvidenceAsset
Content io.ReadCloser
}
func NewExecutionResultService(
repository ExecutionResultRepository,
store ReferenceImageStore,
clock Clock,
ids IDGenerator,
) (*ExecutionResultService, error) {
if repository == nil || store == nil || clock == nil || ids == nil {
return nil, errors.New("execution result service dependencies are required")
}
return &ExecutionResultService{
repository: repository,
store: store,
clock: clock,
ids: ids,
}, nil
}
func (service *ExecutionResultService) AppendEvents(
ctx context.Context,
command AppendExecutionEventsCommand,
) (bool, error) {
identity, err := normalizeExecutionIdentity(command.Identity)
if err != nil {
return false, err
}
if len(command.Events) < 1 || len(command.Events) > 32 {
return false, executionResultInvalid("events", "must contain 1 to 32 events")
}
now := service.clock.Now().UTC()
events := make([]domain.ExecutionEvent, 0, len(command.Events))
seen := make(map[string]struct{}, len(command.Events))
for _, event := range command.Events {
parsed, eventErr := normalizeClientEvent(event, identity.TaskID, identity.ExecutionID, now)
if eventErr != nil {
return false, eventErr
}
if _, found := seen[parsed.ID]; found {
return false, executionResultInvalid("events", "event_id must be unique")
}
seen[parsed.ID] = struct{}{}
events = append(events, parsed)
}
requestHash, err := executionResultHash(command.Events)
if err != nil {
return false, internalExecutionResultFailure(err)
}
replayed, err := service.repository.AppendExecutionEvents(
ctx,
service.write(identity, executionResultEventsOperation, requestHash, now),
events,
)
if err != nil {
return false, wrapLifecycleRepositoryError(err)
}
return replayed, nil
}
func (service *ExecutionResultService) UploadEvidence(
ctx context.Context,
command UploadExecutionEvidenceCommand,
) (UploadExecutionEvidenceResult, error) {
identity, err := normalizeExecutionIdentity(command.Identity)
if err != nil {
return UploadExecutionEvidenceResult{}, err
}
if command.Content == nil {
return UploadExecutionEvidenceResult{}, executionResultInvalid("file", "required")
}
evidenceID, err := service.ids.NewID()
if err != nil {
return UploadExecutionEvidenceResult{}, internalExecutionResultFailure(err)
}
normalized, err := service.store.Put(
ctx,
evidenceID,
command.DeclaredMediaType,
command.Content,
)
if err != nil {
return UploadExecutionEvidenceResult{}, mapImageStoreError(err)
}
cleanup := func() {
_ = service.store.Delete(context.Background(), normalized.StorageKey)
}
now := service.clock.Now().UTC()
requestHash, err := executionResultHash(struct {
InputSHA256 string `json:"input_sha256"`
}{InputSHA256: normalized.InputSHA256})
if err != nil {
cleanup()
return UploadExecutionEvidenceResult{}, internalExecutionResultFailure(err)
}
evidence, replayed, err := service.repository.CreateExecutionEvidence(
ctx,
service.write(identity, executionResultEvidenceOperation, requestHash, now),
domain.ExecutionEvidenceAsset{
ID: evidenceID,
TaskID: identity.TaskID,
ExecutionID: identity.ExecutionID,
MediaType: normalized.MediaType,
SizeBytes: normalized.SizeBytes,
SHA256: normalized.SHA256,
StorageKey: normalized.StorageKey,
CreatedAt: now,
},
)
if err != nil {
cleanup()
return UploadExecutionEvidenceResult{}, wrapLifecycleRepositoryError(err)
}
if replayed {
cleanup()
}
return UploadExecutionEvidenceResult{Evidence: evidence, Replayed: replayed}, nil
}
func (service *ExecutionResultService) StoreCandidates(
ctx context.Context,
command StoreExecutionCandidatesCommand,
) (bool, error) {
identity, err := normalizeExecutionIdentity(command.Identity)
if err != nil {
return false, err
}
if err := validateCandidateCommand(command); err != nil {
return false, err
}
provenanceJSON, candidatesJSON, recommendationJSON, err := candidateJSON(command)
if err != nil {
return false, internalExecutionResultFailure(err)
}
now := service.clock.Now().UTC()
requestHash, err := executionResultHash(command)
if err != nil {
return false, internalExecutionResultFailure(err)
}
replayed, err := service.repository.StoreExecutionCandidates(
ctx,
service.write(identity, executionResultCandidatesOperation, requestHash, now),
domain.ExecutionCandidateBatch{
TaskID: identity.TaskID,
ExecutionID: identity.ExecutionID,
TaskContentSHA256: command.TaskContentSHA256,
ExecutionMode: command.ExecutionMode,
SearchQuery: strings.TrimSpace(command.SearchQuery),
ProvenanceJSON: provenanceJSON,
CandidatesJSON: candidatesJSON,
RecommendationJSON: recommendationJSON,
ReceivedAt: now,
},
)
if err != nil {
return false, wrapLifecycleRepositoryError(err)
}
return replayed, nil
}
func (service *ExecutionResultService) Complete(
ctx context.Context,
command CompleteExecutionCommand,
) (domain.PurchaseTask, bool, error) {
identity, err := normalizeExecutionIdentity(command.Identity)
if err != nil {
return domain.PurchaseTask{}, false, err
}
if err := validateCompleteCommand(command); err != nil {
return domain.PurchaseTask{}, false, err
}
selectedJSON, err := optionalJSON(command.Candidate)
if err != nil {
return domain.PurchaseTask{}, false, internalExecutionResultFailure(err)
}
now := service.clock.Now().UTC()
requestHash, err := executionResultHash(command)
if err != nil {
return domain.PurchaseTask{}, false, internalExecutionResultFailure(err)
}
mode := command.ExecutionMode
taskHash := command.TaskContentSHA256
outcome := command.Outcome
reason := strings.TrimSpace(command.OperatorReason)
task, replayed, err := service.repository.CompleteExecution(
ctx,
service.write(identity, executionResultCompleteOperation, requestHash, now),
domain.ExecutionOutcome{
TaskID: identity.TaskID,
ExecutionID: identity.ExecutionID,
ResultType: "COMPLETE",
ExecutionMode: &mode,
TaskContentSHA256: &taskHash,
Outcome: &outcome,
OperatorReason: &reason,
SelectedCandidateJSON: selectedJSON,
OrderSubmitted: false,
ReceivedAt: now,
},
)
if err != nil {
return domain.PurchaseTask{}, false, wrapLifecycleRepositoryError(err)
}
return task, replayed, nil
}
func (service *ExecutionResultService) Fail(
ctx context.Context,
command FailExecutionCommand,
) (domain.PurchaseTask, bool, error) {
identity, err := normalizeExecutionIdentity(command.Identity)
if err != nil {
return domain.PurchaseTask{}, false, err
}
if err := validateFailCommand(command); err != nil {
return domain.PurchaseTask{}, false, err
}
evidenceJSON, err := optionalJSON(command.EvidenceAssetIDs)
if err != nil {
return domain.PurchaseTask{}, false, internalExecutionResultFailure(err)
}
now := service.clock.Now().UTC()
requestHash, err := executionResultHash(command)
if err != nil {
return domain.PurchaseTask{}, false, internalExecutionResultFailure(err)
}
code := strings.TrimSpace(command.ErrorCode)
message := strings.TrimSpace(command.ErrorMessage)
step := strings.TrimSpace(command.ErrorStep)
retryable := command.Retryable
task, replayed, err := service.repository.FailExecution(
ctx,
service.write(identity, executionResultFailOperation, requestHash, now),
domain.ExecutionOutcome{
TaskID: identity.TaskID,
ExecutionID: identity.ExecutionID,
ResultType: "FAIL",
EvidenceAssetIDsJSON: evidenceJSON,
ErrorCode: &code,
ErrorMessage: &message,
ErrorStep: &step,
Retryable: &retryable,
OrderSubmitted: false,
ReceivedAt: now,
},
)
if err != nil {
return domain.PurchaseTask{}, false, wrapLifecycleRepositoryError(err)
}
return task, replayed, nil
}
func (service *ExecutionResultService) OpenEvidence(
ctx context.Context,
taskID string,
evidenceID string,
) (ExecutionEvidenceContent, error) {
evidence, err := service.repository.GetExecutionEvidence(ctx, taskID, evidenceID)
if err != nil {
return ExecutionEvidenceContent{}, wrapLifecycleRepositoryError(err)
}
content, err := service.store.Open(ctx, evidence.StorageKey)
if err != nil {
return ExecutionEvidenceContent{}, mapImageStoreError(err)
}
return ExecutionEvidenceContent{Evidence: evidence, Content: content}, nil
}
func (service *ExecutionResultService) write(
identity ExecutionResultIdentity,
operation string,
requestHash string,
now time.Time,
) ExecutionResultWrite {
return ExecutionResultWrite{
ExecutionResultAuthorization: ExecutionResultAuthorization{
UserID: identity.UserID,
DeviceID: identity.DeviceID,
TaskID: identity.TaskID,
ExecutionID: identity.ExecutionID,
ClaimGeneration: identity.ClaimGeneration,
ClaimTokenHash: hashSecret(identity.ClaimToken),
},
Operation: operation,
IdempotencyKey: identity.IdempotencyKey,
RequestHash: requestHash,
Now: now,
}
}
func normalizeExecutionIdentity(
identity ExecutionResultIdentity,
) (ExecutionResultIdentity, error) {
identity.UserID = strings.TrimSpace(identity.UserID)
identity.DeviceID = strings.TrimSpace(identity.DeviceID)
identity.TaskID = strings.TrimSpace(identity.TaskID)
identity.ExecutionID = strings.TrimSpace(identity.ExecutionID)
identity.IdempotencyKey = strings.TrimSpace(identity.IdempotencyKey)
fields := map[string]string{}
if !isUUID(identity.UserID) {
fields["user_id"] = "must be a UUID"
}
if !isUUID(identity.DeviceID) {
fields["device_id"] = "must be a UUID"
}
if !isUUID(identity.TaskID) {
fields["task_id"] = "must be a UUID"
}
if !isUUID(identity.ExecutionID) {
fields["execution_id"] = "must be a UUID"
}
if identity.ClaimGeneration < 1 {
fields["claim_generation"] = "must be a positive integer"
}
if strings.TrimSpace(identity.ClaimToken) == "" {
fields["claim_token"] = "required"
}
if len([]byte(identity.IdempotencyKey)) == 0 ||
len([]byte(identity.IdempotencyKey)) > maxIdempotencyKeyBytes ||
!isPrintableASCII(identity.IdempotencyKey) {
fields["idempotency_key"] = "must be printable ASCII up to 128 bytes"
}
if len(fields) > 0 {
return ExecutionResultIdentity{}, invalidError(
"EXECUTION_RESULT_INVALID",
"execution result request is invalid",
fields,
)
}
return identity, nil
}
func normalizeClientEvent(
event ClientExecutionEvent,
taskID string,
executionID string,
now time.Time,
) (domain.ExecutionEvent, error) {
if !isUUID(strings.TrimSpace(event.ID)) {
return domain.ExecutionEvent{}, executionResultInvalid("event_id", "must be a UUID")
}
step := strings.TrimSpace(event.Step)
typeValue := strings.TrimSpace(event.Type)
message := strings.TrimSpace(event.Message)
if !validLifecycleStep(step) {
return domain.ExecutionEvent{}, executionResultInvalid("step", "must be uppercase ASCII")
}
if !validLifecycleStep(typeValue) {
return domain.ExecutionEvent{}, executionResultInvalid("type", "must be uppercase ASCII")
}
if !validAuditText(message, 1000) {
return domain.ExecutionEvent{}, executionResultInvalid("message", "is invalid or contains sensitive material")
}
occurredAt, err := time.Parse(time.RFC3339Nano, strings.TrimSpace(event.OccurredAt))
if err != nil || occurredAt.After(now.Add(5*time.Minute)) ||
occurredAt.Before(now.Add(-7*24*time.Hour)) {
return domain.ExecutionEvent{}, executionResultInvalid("occurred_at", "must be a recent RFC3339 timestamp")
}
return domain.ExecutionEvent{
ID: strings.TrimSpace(event.ID),
TaskID: taskID,
ExecutionID: executionID,
Step: step,
Type: typeValue,
Message: message,
OccurredAt: occurredAt.UTC(),
ReceivedAt: now,
}, nil
}
func validateCandidateCommand(command StoreExecutionCandidatesCommand) error {
if !sha256Pattern.MatchString(command.TaskContentSHA256) {
return executionResultInvalid("task_content_sha256", "must be lowercase SHA-256")
}
if !validExecutionMode(command.ExecutionMode) {
return executionResultInvalid("execution_mode", "must be MANUAL_FIRST or AI_ASSISTED")
}
if !validAuditText(command.SearchQuery, 512) {
return executionResultInvalid("search_query", "is invalid")
}
if len(command.Candidates) > 5 {
return executionResultInvalid("candidates", "must contain at most 5 candidates")
}
if command.ExecutionMode == aiAssistedMode {
if !validProvenance(command.Provenance) {
return executionResultInvalid("provenance", "is required for AI_ASSISTED")
}
} else if command.Provenance != nil {
return executionResultInvalid("provenance", "must be omitted for MANUAL_FIRST")
}
strictSKUMatching := command.ExecutionMode == aiAssistedMode &&
command.Provenance.SchemaVersion >= 2
for index, candidate := range command.Candidates {
if candidate.Ordinal != index+1 ||
!validCandidate(candidate, command.ExecutionMode) ||
(strictSKUMatching && !validSKUMatchedCandidate(candidate)) {
return executionResultInvalid("candidates", "must be continuous, bounded observations")
}
}
if strictSKUMatching && len(command.Candidates) > 0 &&
(command.Recommendation == nil ||
command.Recommendation.CandidateOrdinal != 1) {
return executionResultInvalid(
"recommendation",
"must select the first sorted SKU-matched candidate",
)
}
if command.Recommendation != nil {
recommendation := command.Recommendation
if recommendation.CandidateOrdinal < 1 ||
recommendation.CandidateOrdinal > len(command.Candidates) ||
!validAuditText(recommendation.PolicyVersion, 128) ||
!validStringList(recommendation.Reasons, 8, 160) {
return executionResultInvalid("recommendation", "is invalid")
}
}
return nil
}
func validateCompleteCommand(command CompleteExecutionCommand) error {
if !sha256Pattern.MatchString(command.TaskContentSHA256) ||
!validExecutionMode(command.ExecutionMode) ||
!validOutcome(command.Outcome) ||
!validAuditText(command.OperatorReason, 1000) ||
command.OrderSubmitted {
return executionResultInvalid("complete", "contains an invalid outcome or order state")
}
if command.Outcome == "CANDIDATE_ACCEPTED" {
if command.Candidate == nil || !validCandidate(*command.Candidate, command.ExecutionMode) {
return executionResultInvalid("candidate", "is required for CANDIDATE_ACCEPTED")
}
} else if command.Candidate != nil {
return executionResultInvalid("candidate", "must be omitted for this outcome")
}
return nil
}
func validateFailCommand(command FailExecutionCommand) error {
if !validLifecycleStep(strings.TrimSpace(command.ErrorCode)) ||
!validLifecycleStep(strings.TrimSpace(command.ErrorStep)) ||
!validAuditText(command.ErrorMessage, 1000) {
return executionResultInvalid("error", "is invalid")
}
if len(command.EvidenceAssetIDs) > 5 {
return executionResultInvalid("evidence_asset_ids", "must contain at most 5 items")
}
for _, id := range command.EvidenceAssetIDs {
if !isUUID(strings.TrimSpace(id)) {
return executionResultInvalid("evidence_asset_ids", "must contain UUIDs")
}
}
return nil
}
func validCandidate(candidate ExecutionCandidate, mode string) bool {
if candidate.Ordinal < 1 ||
!validAuditText(candidate.Title, 512) ||
!validOptionalAuditText(candidate.SKUText, 512) ||
!validOptionalAuditText(candidate.Price, 64) ||
!validObservationURL(candidate.ProductURL) ||
!validObservationURL(candidate.ImageURL) ||
len(candidate.EvidenceAssetIDs) > 5 {
return false
}
for _, id := range candidate.EvidenceAssetIDs {
if !isUUID(strings.TrimSpace(id)) {
return false
}
}
if mode == manualFirstMode {
return candidate.Evaluation == nil
}
return validEvaluation(candidate.Evaluation)
}
func validEvaluation(value *CandidateEvaluation) bool {
if value == nil || value.Score < 0 || value.Score > 1 ||
value.Confidence < 0 || value.Confidence > 1 {
return false
}
if value.Decision != "REVIEW" && value.Decision != "REJECT" &&
value.Decision != "MANUAL_REQUIRED" {
return false
}
return validStringList(value.Matched, 12, 160) &&
validStringList(value.MissingOrUncertain, 12, 160) &&
validStringList(value.RejectionReasons, 12, 160) &&
validCandidateHardConstraints(value.HardConstraints)
}
func validSKUMatchedCandidate(candidate ExecutionCandidate) bool {
value := candidate.Evaluation
return value != nil &&
value.Decision == "REVIEW" &&
value.Score >= 0.75 &&
value.Confidence >= 0.75 &&
len(value.RejectionReasons) == 0 &&
len(value.HardConstraints) == 2
}
func validCandidateHardConstraints(
values []CandidateHardConstraintEvaluation,
) bool {
if len(values) == 0 {
return true
}
if len(values) != 2 {
return false
}
seen := map[string]struct{}{}
for _, value := range values {
if (value.Kind != "COLOR" && value.Kind != "SIZE") ||
value.Status != "MATCH" ||
!validAuditText(value.Expected, 128) ||
!validAuditText(value.Evidence, 160) {
return false
}
if _, duplicate := seen[value.Kind]; duplicate {
return false
}
seen[value.Kind] = struct{}{}
}
return len(seen) == 2
}
func validProvenance(value *ExecutionProvenance) bool {
return value != nil &&
validAuditText(value.ProviderID, 64) &&
validAuditText(value.Model, 256) &&
validAuditText(value.PromptVersion, 128) &&
value.SchemaVersion >= 1 && value.SchemaVersion <= 32
}
func validExecutionMode(value string) bool {
return value == manualFirstMode || value == aiAssistedMode
}
func validOutcome(value string) bool {
switch value {
case "CANDIDATE_ACCEPTED", "CANDIDATE_REJECTED", "NO_MATCH", "MANUAL_REQUIRED":
return true
default:
return false
}
}
func validObservationURL(value string) bool {
if strings.TrimSpace(value) == "" {
return true
}
parsed, err := url.Parse(strings.TrimSpace(value))
return err == nil &&
(parsed.Scheme == "https" || parsed.Scheme == "http") &&
parsed.Host != "" &&
parsed.User == nil &&
len(value) <= 2048
}
func validAuditText(value string, maximum int) bool {
value = strings.TrimSpace(value)
if value == "" || !utf8.ValidString(value) || len([]byte(value)) > maximum {
return false
}
lower := strings.ToLower(value)
return !strings.Contains(lower, "authorization:") &&
!strings.Contains(lower, "api_key") &&
!strings.Contains(lower, "bearer ")
}
func validOptionalAuditText(value string, maximum int) bool {
return strings.TrimSpace(value) == "" || validAuditText(value, maximum)
}
func validStringList(values []string, maximumItems int, maximumText int) bool {
if len(values) > maximumItems {
return false
}
seen := map[string]struct{}{}
for _, value := range values {
if !validAuditText(value, maximumText) {
return false
}
key := strings.ToLower(strings.TrimSpace(value))
if _, duplicate := seen[key]; duplicate {
return false
}
seen[key] = struct{}{}
}
return true
}
func candidateJSON(command StoreExecutionCandidatesCommand) (*string, string, *string, error) {
var provenance *string
if command.Provenance != nil {
encoded, err := json.Marshal(command.Provenance)
if err != nil {
return nil, "", nil, err
}
value := string(encoded)
provenance = &value
}
candidates, err := json.Marshal(command.Candidates)
if err != nil {
return nil, "", nil, err
}
var recommendation *string
if command.Recommendation != nil {
encoded, err := json.Marshal(command.Recommendation)
if err != nil {
return nil, "", nil, err
}
value := string(encoded)
recommendation = &value
}
return provenance, string(candidates), recommendation, nil
}
func optionalJSON(value any) (*string, error) {
if value == nil {
return nil, nil
}
encoded, err := json.Marshal(value)
if err != nil {
return nil, err
}
result := string(encoded)
return &result, nil
}
func executionResultHash(value any) (string, error) {
encoded, err := json.Marshal(value)
if err != nil {
return "", err
}
sum := sha256.Sum256(encoded)
return hex.EncodeToString(sum[:]), nil
}
func TaskContentSHA256(task domain.PurchaseTask) string {
budget := ""
if task.MaxBudgetCents != nil {
budget = strconv.FormatInt(*task.MaxBudgetCents, 10)
}
payload := strings.Join([]string{
task.Title,
task.Description,
task.SKU,
task.ImageAssetID,
strconv.Itoa(task.Quantity),
budget,
task.Currency,
}, "\x00")
sum := sha256.Sum256([]byte(payload))
return hex.EncodeToString(sum[:])
}
func executionResultInvalid(field string, message string) error {
return invalidError(
"EXECUTION_RESULT_INVALID",
"execution result request is invalid",
map[string]string{field: message},
)
}
func internalExecutionResultFailure(err error) error {
return newError(
ErrorKindInternal,
"INTERNAL_ERROR",
"internal server error",
err,
)
}
var sha256Pattern = regexp.MustCompile("^[0-9a-f]{64}$")