Files
go-sip/internal/store/control_routes.go
T

67 lines
2.3 KiB
Go

package store
import (
"bytes"
"database/sql"
"errors"
agentv1 "git.ipao.vip/rogee/go-sip/gen/agent/v1"
"google.golang.org/protobuf/proto"
)
// BindExecutionAgent records the controlled endpoint identity before any remote
// execution. An execution may not be reassigned after a lost response.
func (s *Store) BindExecutionAgent(agentID string, binding *agentv1.ExecutionBinding) error {
if err := validateExecutionAgentBinding(agentID, binding); err != nil {
return err
}
s.mu.Lock()
defer s.mu.Unlock()
tx, err := s.db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
if err := bindExecutionAgentTx(tx, agentID, binding); err != nil {
return err
}
return tx.Commit()
}
func validateExecutionAgentBinding(agentID string, binding *agentv1.ExecutionBinding) error {
if agentID == "" || binding == nil {
return errors.New("agent and execution binding are required")
}
return nil
}
// bindExecutionAgentTx is shared by the legacy control route and the local
// Mock origination claim, which must commit its Agent assignment atomically.
func bindExecutionAgentTx(tx *sql.Tx, agentID string, binding *agentv1.ExecutionBinding) error {
encoded, err := (proto.MarshalOptions{Deterministic: true}).Marshal(binding)
if err != nil {
return err
}
var count int
if err := tx.QueryRow(`SELECT COUNT(*) FROM tasks WHERE execution_id=? AND tenant_id=? AND tenant_key=? AND task_id=? AND task_item_id=? AND task_revision=? AND agent_version_id=? AND route_policy_id=? AND caller_profile_id=? AND status='reserved'`, binding.ExecutionId, binding.TenantId, binding.TenantKey, binding.TaskId, binding.TaskItemId, binding.TaskRevision, binding.AgentVersionId, binding.RoutePolicyId, binding.CallerProfileId).Scan(&count); err != nil {
return err
}
if count != 1 {
return ErrCASConflict
}
var previousAgent string
var previousBinding []byte
err = tx.QueryRow(`SELECT agent_id,binding FROM execution_agents WHERE execution_id=?`, binding.ExecutionId).Scan(&previousAgent, &previousBinding)
if err == nil {
if previousAgent != agentID || !bytes.Equal(previousBinding, encoded) {
return ErrIdempotencyConflict
}
return nil
}
if !errors.Is(err, sql.ErrNoRows) {
return err
}
_, err = tx.Exec(`INSERT INTO execution_agents(execution_id,agent_id,binding) VALUES(?,?,?)`, binding.ExecutionId, agentID, encoded)
return err
}