Feat v0.7.10 workflow handoff + assignment UI
Some checks failed
CI/CD / test-sqlite (pull_request) Has been cancelled
CI/CD / build-and-deploy (pull_request) Has been cancelled
CI/CD / test-runners (pull_request) Has been skipped
CI/CD / test-frontend (pull_request) Has been cancelled
CI/CD / detect-changes (pull_request) Successful in 4s
CI/CD / test-go-pg (pull_request) Has been cancelled
CI/CD / e2e-smoke (pull_request) Has been cancelled

Public→team stage handoff with audience mismatch detection, enriched
assignment API responses (workflow_name, stage_name, sla_breached),
team inbox with claim/assign actions, assignment notifications, team
middleware system-admin bypass fix, and SDK gap closure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-02 23:15:40 +00:00
parent a9cf71b76d
commit 8f8c1b0e53
17 changed files with 818 additions and 36 deletions

View File

@@ -517,6 +517,52 @@ func (s *WorkflowStore) ListInstances(ctx context.Context, workflowID string, st
return result, rows.Err()
}
func (s *WorkflowStore) ListInstancesByTeam(ctx context.Context, teamID string, status string, opts store.ListOptions) ([]models.WorkflowInstance, error) {
q := `SELECT i.id, i.workflow_id, i.workflow_version, i.current_stage, i.stage_data,
i.status, i.started_by, i.entry_token, i.metadata,
i.stage_entered_at, i.created_at, i.updated_at
FROM workflow_instances i
JOIN workflows w ON w.id = i.workflow_id
WHERE w.team_id = $1`
args := []interface{}{teamID}
idx := 2
if status != "" {
q += fmt.Sprintf(" AND i.status = $%d", idx)
args = append(args, status)
idx++
}
q += " ORDER BY i.created_at DESC"
if opts.Limit > 0 {
q += fmt.Sprintf(" LIMIT $%d", idx)
args = append(args, opts.Limit)
idx++
}
if opts.Offset > 0 {
q += fmt.Sprintf(" OFFSET $%d", idx)
args = append(args, opts.Offset)
}
rows, err := DB.QueryContext(ctx, q, args...)
if err != nil {
return nil, err
}
defer rows.Close()
var result []models.WorkflowInstance
for rows.Next() {
var inst models.WorkflowInstance
var stageData, metadata []byte
if err := rows.Scan(&inst.ID, &inst.WorkflowID, &inst.WorkflowVersion,
&inst.CurrentStage, &stageData, &inst.Status, &inst.StartedBy,
&inst.EntryToken, &metadata,
&inst.StageEnteredAt, &inst.CreatedAt, &inst.UpdatedAt); err != nil {
return nil, err
}
inst.StageData = stageData
inst.Metadata = metadata
result = append(result, inst)
}
return result, rows.Err()
}
func (s *WorkflowStore) AdvanceStage(ctx context.Context, id string, nextStage string, stageData json.RawMessage) error {
sd := jsonOrEmpty(stageData)
_, err := DB.ExecContext(ctx, `

View File

@@ -534,6 +534,49 @@ func (s *WorkflowStore) ListInstances(ctx context.Context, workflowID string, st
return result, rows.Err()
}
func (s *WorkflowStore) ListInstancesByTeam(ctx context.Context, teamID string, status string, opts store.ListOptions) ([]models.WorkflowInstance, error) {
q := `SELECT i.id, i.workflow_id, i.workflow_version, i.current_stage, i.stage_data,
i.status, i.started_by, i.entry_token, i.metadata,
i.stage_entered_at, i.created_at, i.updated_at
FROM workflow_instances i
JOIN workflows w ON w.id = i.workflow_id
WHERE w.team_id = ?`
args := []interface{}{teamID}
if status != "" {
q += " AND i.status = ?"
args = append(args, status)
}
q += " ORDER BY i.created_at DESC"
if opts.Limit > 0 {
q += " LIMIT ?"
args = append(args, opts.Limit)
}
if opts.Offset > 0 {
q += " OFFSET ?"
args = append(args, opts.Offset)
}
rows, err := DB.QueryContext(ctx, q, args...)
if err != nil {
return nil, err
}
defer rows.Close()
var result []models.WorkflowInstance
for rows.Next() {
var inst models.WorkflowInstance
var stageData, metadata string
if err := rows.Scan(&inst.ID, &inst.WorkflowID, &inst.WorkflowVersion,
&inst.CurrentStage, &stageData, &inst.Status, &inst.StartedBy,
&inst.EntryToken, &metadata,
st(&inst.StageEnteredAt), st(&inst.CreatedAt), st(&inst.UpdatedAt)); err != nil {
return nil, err
}
inst.StageData = json.RawMessage(stageData)
inst.Metadata = json.RawMessage(metadata)
result = append(result, inst)
}
return result, rows.Err()
}
func (s *WorkflowStore) AdvanceStage(ctx context.Context, id string, nextStage string, stageData json.RawMessage) error {
sd := jsonOrEmpty(stageData)
now := time.Now().UTC()

View File

@@ -42,6 +42,7 @@ type WorkflowStore interface {
CancelInstance(ctx context.Context, id string) error
MarkInstanceStale(ctx context.Context, id string) error
ListActiveInstances(ctx context.Context) ([]models.WorkflowInstance, error)
ListInstancesByTeam(ctx context.Context, teamID string, status string, opts ListOptions) ([]models.WorkflowInstance, error)
// Assignments
CreateAssignment(ctx context.Context, a *models.WorkflowAssignment) error