docs(milestone): complete scheduling-streaming (v0.3)

---ci---
project: orca
phase: 3
milestone: v0.3
status: complete
requirements:
  covered: [REQ-022, REQ-030, REQ-032]
  partial: []
---/ci---

v0.3 milestone merged to main. Includes all v0.2 work (P08-P10) that
was previously on the milestone branch but not yet merged to main, plus
the v0.3 completion work (iter.Seq streaming + doctor network/db).

v0.2 phases included: P08 (mTLS), P09 (scheduling), P10 (security scan).
v0.3 phases: P0 (pre-execution), P1 (iter.Seq streaming), P2 (doctor),
P3 (final review+ship).

Total: 40 requirements, all complete. No new go.mod dependencies.
Full test suite passes under -race. gofmt + go vet clean.
This commit is contained in:
Jon Chery
2026-08-01 20:06:47 +00:00
parent f503404dda
commit df58bc25a3
52 changed files with 5488 additions and 198 deletions
+61
View File
@@ -3,6 +3,8 @@ package engine
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
"os/exec"
@@ -29,6 +31,65 @@ func NewExecutor(jobs *store.JobRepo, tasks *store.TaskRepo, log *slog.Logger) *
return &Executor{jobs: jobs, tasks: tasks, log: log}
}
// Submit is the dispatch-friendly entry point (v0.2 P02). It parses
// the spec bytes as a minimal TaskSpec and runs a single task under
// a fresh job. Returns the job ID. This is intentionally simpler
// than the v0.1 Run() entry point — the cross-node dispatch wire
// format is a flat task (one process), not a multi-task job.
//
// The spec format is a JSON object with at least:
//
// { "name": "...", "command": "...", "args": [...], "env": [...] }
//
// All fields except command are optional.
func (e *Executor) Submit(ctx context.Context, specBytes []byte) (string, error) {
type wireSpec struct {
Name string `json:"name"`
Command string `json:"command"`
Args []string `json:"args"`
Env []string `json:"env"`
}
var ws wireSpec
if err := json.Unmarshal(specBytes, &ws); err != nil {
return "", fmt.Errorf("Executor.Submit: parse: %w", err)
}
if ws.Command == "" {
return "", errors.New("Executor.Submit: spec.command is required")
}
if ws.Name == "" {
ws.Name = "dispatched"
}
job := &model.Job{
ID: uuid.NewString(),
Spec: string(specBytes),
Status: model.JobStatusPending,
}
ts := TaskSpec{
Name: ws.Name,
Command: ws.Command,
Args: ws.Args,
Env: ws.Env,
}
if err := e.Run(ctx, job, []TaskSpec{ts}); err != nil {
return job.ID, err
}
return job.ID, nil
}
// Status returns the current state of a job for the Status dispatch
// endpoint. The returned string is one of: "pending", "running",
// "complete", "failed", "stopped". Maps to model.JobStatus* values.
func (e *Executor) Status(ctx context.Context, jobID string) (string, error) {
if e.jobs == nil {
return "", errors.New("Executor.Status: nil job repo")
}
j, err := e.jobs.Get(ctx, jobID)
if err != nil {
return "", err
}
return string(j.Status), nil
}
type TaskSpec struct {
Name string
Command string