Drop workflow kinds, deliver reviews when done, rename/delete workflows

This commit is contained in:
Ian Gulliver
2026-10-20 20:09:38 -07:00
parent 51df9adf61
commit 35f59246f7
17 changed files with 671 additions and 531 deletions
@@ -0,0 +1,11 @@
UPDATE workflows SET definition = json_remove(definition, '$.kind')
WHERE json_type(definition, '$.kind') IS NOT NULL;
UPDATE tasks SET workflow_definition = json_remove(workflow_definition, '$.kind')
WHERE workflow_definition != '' AND json_type(workflow_definition, '$.kind') IS NOT NULL;
UPDATE reviews SET definition = json_remove(definition, '$.kind')
WHERE definition != '' AND json_type(definition, '$.kind') IS NOT NULL;
UPDATE events SET payload = json_set(payload, '$.kept', json_array(task_id))
WHERE kind = 'task.workflow_finished' AND json_type(payload, '$.kept') IS NULL;
@@ -0,0 +1,25 @@
CREATE TABLE reviews_next (
id TEXT PRIMARY KEY,
task_id TEXT NOT NULL REFERENCES tasks(id),
snapshot_n INTEGER NOT NULL,
agent_session_id TEXT REFERENCES agent_sessions(id),
vendor TEXT NOT NULL,
model TEXT NOT NULL,
state TEXT NOT NULL,
result TEXT NOT NULL DEFAULT '',
error TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL,
finished_at TEXT NOT NULL DEFAULT '',
finding_states TEXT NOT NULL DEFAULT '{}',
finding_responses TEXT NOT NULL DEFAULT '{}',
stage TEXT NOT NULL DEFAULT '',
definition TEXT NOT NULL DEFAULT '',
UNIQUE (task_id, snapshot_n, stage)
);
INSERT INTO reviews_next (id, task_id, snapshot_n, agent_session_id, vendor, model, state, result, error, created_at, finished_at, finding_states, finding_responses, stage, definition)
SELECT id, task_id, snapshot_n, agent_session_id, vendor, model, state, result, error, created_at, finished_at, finding_states, finding_responses, stage, definition FROM reviews;
DROP TABLE reviews;
ALTER TABLE reviews_next RENAME TO reviews;
CREATE UNIQUE INDEX reviews_session ON reviews(agent_session_id);
+70 -5
View File
@@ -18,7 +18,7 @@ type FrozenWorkflow struct {
}
func (s *Store) ListWorkflows(ctx context.Context) ([]api.WorkflowSummary, error) {
rows, err := s.db.QueryContext(ctx, `SELECT name, coalesce(json_extract(definition, '$.description'), ''), coalesce(nullif(json_extract(definition, '$.kind'), ''), 'task') FROM workflows ORDER BY name`)
rows, err := s.db.QueryContext(ctx, `SELECT name, coalesce(json_extract(definition, '$.description'), '') FROM workflows ORDER BY name`)
if err != nil {
return nil, err
}
@@ -27,7 +27,7 @@ func (s *Store) ListWorkflows(ctx context.Context) ([]api.WorkflowSummary, error
workflows := []api.WorkflowSummary{}
for rows.Next() {
w := api.WorkflowSummary{}
err = rows.Scan(&w.Name, &w.Description, &w.Kind)
err = rows.Scan(&w.Name, &w.Description)
if err != nil {
return nil, err
}
@@ -50,12 +50,46 @@ func (s *Store) GetWorkflow(ctx context.Context, name string) (api.WorkflowDefin
return w, err
}
func (s *Store) PutWorkflow(ctx context.Context, name string, def api.WorkflowDefinition, actor string) error {
var ErrWorkflowExists = errors.New("workflow exists")
func (s *Store) PutWorkflow(ctx context.Context, name, from string, def api.WorkflowDefinition, actor string) error {
b, err := json.Marshal(def)
if err != nil {
return err
}
return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
if from != "" {
found := false
err := tx.QueryRowContext(ctx, `SELECT EXISTS (SELECT 1 FROM workflows WHERE name = ?)`, from).Scan(&found)
if err != nil {
return err
}
if !found {
return fmt.Errorf("%w: no workflow %q", ErrNotFound, from)
}
}
if from != "" && from != name {
taken := false
err := tx.QueryRowContext(ctx, `SELECT EXISTS (SELECT 1 FROM workflows WHERE name = ?)`, name).Scan(&taken)
if err != nil {
return err
}
if taken {
return fmt.Errorf("%w: a workflow named %q already exists", ErrWorkflowExists, name)
}
_, err = tx.ExecContext(ctx, `DELETE FROM workflows WHERE name = ?`, from)
if err != nil {
return err
}
err = repointReviews(ctx, tx, emit, from, name, actor)
if err != nil {
return err
}
err = emit(api.Event{Kind: "workflow.deleted", Actor: actor, Payload: map[string]any{"name": from}})
if err != nil {
return err
}
}
_, err := tx.ExecContext(ctx, `INSERT INTO workflows (name, definition) VALUES (?, ?)
ON CONFLICT (name) DO UPDATE SET definition = excluded.definition`, name, string(b))
if err != nil {
@@ -65,6 +99,33 @@ func (s *Store) PutWorkflow(ctx context.Context, name string, def api.WorkflowDe
})
}
func repointReviews(ctx context.Context, tx *sql.Tx, emit emitFunc, from, to, actor string) error {
rows, err := tx.QueryContext(ctx, `UPDATE projects SET review_workflow = ? WHERE review_workflow = ? RETURNING `+projectColumns, to, from)
if err != nil {
return err
}
projects := []api.Project{}
for rows.Next() {
p, err := scanProject(rows)
if err != nil {
rows.Close()
return err
}
projects = append(projects, p)
}
err = errors.Join(rows.Err(), rows.Close())
if err != nil {
return err
}
for _, p := range projects {
err = emit(api.Event{Kind: "project.updated", ProjectId: &p.Id, Actor: actor, Payload: ToPayload(p)})
if err != nil {
return err
}
}
return nil
}
func (s *Store) DeleteWorkflow(ctx context.Context, name, actor string) error {
return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
res, err := tx.ExecContext(ctx, `DELETE FROM workflows WHERE name = ?`, name)
@@ -78,6 +139,10 @@ func (s *Store) DeleteWorkflow(ctx context.Context, name, actor string) error {
if n == 0 {
return fmt.Errorf("%w: no workflow %q", ErrNotFound, name)
}
err = repointReviews(ctx, tx, emit, name, "", actor)
if err != nil {
return err
}
return emit(api.Event{Kind: "workflow.deleted", Actor: actor, Payload: map[string]any{"name": name}})
})
}
@@ -201,7 +266,7 @@ func clearStage(ctx context.Context, tx *sql.Tx, sessionID string) error {
return err
}
func (s *Store) FinishWorkflow(ctx context.Context, task api.Task, sessionID, workflowName, output string) error {
func (s *Store) FinishWorkflow(ctx context.Context, task api.Task, sessionID, workflowName, output string, kept []string) error {
return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
err := clearStage(ctx, tx, sessionID)
if err != nil {
@@ -212,7 +277,7 @@ func (s *Store) FinishWorkflow(ctx context.Context, task api.Task, sessionID, wo
ProjectId: &task.ProjectId,
TaskId: &task.Id,
Actor: "byre",
Payload: map[string]any{"id": task.Id, "workflow": workflowName, "output": output},
Payload: map[string]any{"id": task.Id, "workflow": workflowName, "output": output, "kept": kept},
})
})
}