feat: start provider ingestion and orchestration coordination
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
// Package orchestrator connects router lease events to an opaque herdr
|
||||
// session. It is deliberately small: scheduling remains in router and the
|
||||
// adapter remains the only component that knows how to drive a harness.
|
||||
package orchestrator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"orchestra/internal/domain"
|
||||
"orchestra/internal/herdr"
|
||||
"orchestra/internal/store"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Worktrees interface {
|
||||
Create(context.Context, domain.Task) (string, error)
|
||||
}
|
||||
type Adapters interface {
|
||||
Adapter(string) (herdr.Adapter, error)
|
||||
}
|
||||
|
||||
type Coordinator struct {
|
||||
Store *store.Store
|
||||
Worktrees Worktrees
|
||||
Adapters Adapters
|
||||
mu sync.Mutex
|
||||
sessions map[string]herdr.Session
|
||||
}
|
||||
|
||||
func (c *Coordinator) Start(ctx context.Context, e domain.Event) error {
|
||||
if e.Type != "TaskLeased" {
|
||||
return nil
|
||||
}
|
||||
if c.Store == nil || c.Worktrees == nil || c.Adapters == nil {
|
||||
return fmt.Errorf("orchestrator: dependencies required")
|
||||
}
|
||||
t, ok := c.Store.Task(e.TaskID)
|
||||
if !ok {
|
||||
return domain.ErrNotFound
|
||||
}
|
||||
var p struct {
|
||||
HarnessID string `json:"harness_id"`
|
||||
HandoffRef string `json:"handoff_ref"`
|
||||
}
|
||||
if err := json.Unmarshal(e.Payload, &p); err != nil || p.HarnessID == "" {
|
||||
return fmt.Errorf("orchestrator: invalid lease")
|
||||
}
|
||||
w, err := c.Worktrees.Create(ctx, t)
|
||||
if err != nil {
|
||||
return c.block(t, "worktree: "+err.Error())
|
||||
}
|
||||
a, err := c.Adapters.Adapter(p.HarnessID)
|
||||
if err != nil {
|
||||
return c.block(t, "adapter: "+err.Error())
|
||||
}
|
||||
s, err := a.Lease(ctx, t.ID, w)
|
||||
if err != nil {
|
||||
return c.block(t, "lease: "+err.Error())
|
||||
}
|
||||
if p.HandoffRef != "" {
|
||||
if err = a.Bootstrap(ctx, s, p.HandoffRef); err != nil {
|
||||
_ = a.Kill(ctx, s)
|
||||
return c.block(t, "bootstrap: "+err.Error())
|
||||
}
|
||||
}
|
||||
c.mu.Lock()
|
||||
if c.sessions == nil {
|
||||
c.sessions = map[string]herdr.Session{}
|
||||
}
|
||||
c.sessions[t.ID] = s
|
||||
c.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Coordinator) block(t domain.Task, reason string) error {
|
||||
b, _ := json.Marshal(map[string]string{"blocker": reason})
|
||||
return c.Store.Append(domain.Event{ID: domain.NewID(), Type: "TaskBlocked", TaskID: t.ID, Version: t.Version + 1, Payload: b})
|
||||
}
|
||||
|
||||
func (c *Coordinator) Session(taskID string) (herdr.Session, bool) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
s, ok := c.sessions[taskID]
|
||||
return s, ok
|
||||
}
|
||||
Reference in New Issue
Block a user