Files
hexis/internal/execution/engine.go
T
kami cca63269e1 Initial commit: Hexis capability registry + execution service baseline
Go daemon (hexisd/hexisctl) implementing capability registry, guarded
execution (confirmations, blessed-entity checks), systemd/workspace-mcp
providers per ECOSYSTEM-SPEC.md. Snapshotting existing working state
before further development.
2026-07-20 00:50:37 +04:00

154 lines
3.9 KiB
Go

package execution
import (
"encoding/json"
"fmt"
"time"
"github.com/kami/hexis/internal/domain"
"github.com/kami/hexis/internal/provider"
"github.com/kami/hexis/internal/storage"
)
type Engine struct {
store storage.Interface
registry *provider.Registry
}
func New(store storage.Interface, registry *provider.Registry) *Engine {
return &Engine{store: store, registry: registry}
}
type ExecuteResult struct {
Execution *domain.Execution `json:"execution"`
}
func (e *Engine) Execute(req *domain.ExecuteRequest) (*ExecuteResult, error) {
if req.IdempotencyKey != "" {
existing, err := e.store.GetExecutionByIdempotencyKey(req.IdempotencyKey)
if err == nil {
return &ExecuteResult{Execution: existing}, nil
}
}
capability, err := e.store.GetCapability(req.CapabilityID)
if err != nil {
return nil, fmt.Errorf("capability: %w", err)
}
if capability.TargetEntityID != "" && capability.TargetEntityID != req.TargetEntityID {
return nil, domain.ErrCapabilityNotBound
}
prov, err := e.registry.Get(capability.Provider)
if err != nil {
return nil, fmt.Errorf("provider: %w", err)
}
now := time.Now().UTC()
exec := &domain.Execution{
ID: domain.NewExecutionID(),
CapabilityID: req.CapabilityID,
TargetEntityID: req.TargetEntityID,
EntityVersion: req.EntityVersion,
Arguments: req.Arguments,
RequestedBy: req.RequestedBy,
Origin: req.Origin,
IdempotencyKey: req.IdempotencyKey,
Status: domain.ExecutionStarted,
CorrelationID: req.CorrelationID,
ResolutionEvidence: req.ResolutionEvidence,
CreatedAt: now,
UpdatedAt: now,
}
if exec.Arguments == nil {
exec.Arguments = map[string]any{}
}
if exec.RequestedBy == nil {
exec.RequestedBy = map[string]string{}
}
if exec.Origin == nil {
exec.Origin = map[string]string{}
}
if err := e.store.CreateExecution(exec); err != nil {
return nil, err
}
e.emitEvent(domain.EventExecutionStarted, exec.ID, map[string]any{
"capability_id": req.CapabilityID,
"target_entity_id": req.TargetEntityID,
"provider": capability.Provider,
"correlation_id": req.CorrelationID,
})
result, execErr := prov.Execute(capability, req)
exec.UpdatedAt = time.Now().UTC()
if execErr != nil {
exec.Status = domain.ExecutionFailed
exec.Error = execErr.Error()
exec.Result = map[string]any{"error": execErr.Error()}
e.emitEvent(domain.EventExecutionFailed, exec.ID, map[string]any{
"capability_id": req.CapabilityID,
"error": execErr.Error(),
})
} else {
exec.Status = domain.ExecutionSucceeded
exec.Result = result
e.emitEvent(domain.EventExecutionSucceeded, exec.ID, map[string]any{
"capability_id": req.CapabilityID,
})
}
if err := e.store.UpdateExecution(exec); err != nil {
return nil, err
}
return &ExecuteResult{Execution: exec}, nil
}
func (e *Engine) ValidateTarget(capabilityID, entityID string) error {
cap, err := e.store.GetCapability(capabilityID)
if err != nil {
return err
}
if cap.TargetEntityID != "" && cap.TargetEntityID != entityID {
return domain.ErrCapabilityNotBound
}
return nil
}
func (e *Engine) emitEvent(evtType domain.HexisEventType, entityID string, payload map[string]any) {
e.store.AppendEvent(&domain.Event{
ID: domain.NewEventID(),
Type: evtType,
Timestamp: time.Now().UTC(),
Payload: payload,
})
}
type ResolveRequest struct {
Query string `json:"query"`
Types []string `json:"types,omitempty"`
}
type ResolveResult struct {
Status string `json:"status"`
Candidates []map[string]any `json:"candidates,omitempty"`
EntityID string `json:"entity_id,omitempty"`
}
var _ json.Marshaler = (*ResolveResult)(nil)
func (r *ResolveResult) MarshalJSON() ([]byte, error) {
m := map[string]any{"status": r.Status}
if r.Candidates != nil {
m["candidates"] = r.Candidates
}
if r.EntityID != "" {
m["entity_id"] = r.EntityID
}
return json.Marshal(m)
}