Feat v0.3.0 workflow schema redesign + stage CRUD modernization
Some checks failed
CI/CD / detect-changes (pull_request) Successful in 4s
CI/CD / test-frontend (pull_request) Successful in 5s
CI/CD / test-go-pg (pull_request) Failing after 1m56s
CI/CD / test-sqlite (pull_request) Successful in 2m47s
CI/CD / build-and-deploy (pull_request) Has been skipped

Drop chat-era columns (persona_id, history_mode) from workflow_stages.
Rename transition_rules → stage_config. Add audience, stage_type,
starlark_hook, branch_rules columns. Update stage_mode CHECK to
(form, review, delegated, automated). All Go stores, handlers,
routing engine, Starlark module, and frontend editors updated.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-27 17:48:14 +00:00
parent 8580e1d93e
commit 1b08769f09
16 changed files with 414 additions and 161 deletions

View File

@@ -8,13 +8,13 @@ import (
"switchboard-core/models"
)
// ── Conditional Routing Engine (v0.35.0) ────
// ── Conditional Routing Engine ───────────────
//
// Evaluates transition_rules.conditions[] against accumulated stage_data
// to determine which stage to advance to. Falls back to ordinal + 1
// when no conditions are defined or none match.
// Evaluates branch_rules[] against accumulated stage_data to determine
// which stage to advance to. Falls back to ordinal + 1 when no rules
// are defined or none match.
// Condition is a single routing condition within transition_rules.
// Condition is a single routing rule within branch_rules.
type Condition struct {
Field string `json:"field"`
Op string `json:"op"`
@@ -22,15 +22,21 @@ type Condition struct {
TargetStage string `json:"target_stage"` // stage name or ordinal as string
}
// TransitionRulesWithConditions extends the existing transition_rules JSON.
type TransitionRulesWithConditions struct {
AutoAssign string `json:"auto_assign,omitempty"`
Conditions []Condition `json:"conditions,omitempty"`
// StageConfig holds non-routing config from stage_config JSON.
type StageConfig struct {
AutoAssign string `json:"auto_assign,omitempty"`
OnAdvance *OnAdvanceReference `json:"on_advance,omitempty"`
}
// ResolveNextStage evaluates conditions from the current stage's transition_rules
// against accumulated stageData. Returns the target stage ordinal.
// Falls back to currentStage + 1 if no conditions match or none are defined.
// OnAdvanceReference points to a Starlark hook for on_advance processing.
type OnAdvanceReference struct {
PackageID string `json:"package_id"`
EntryPoint string `json:"entry_point"`
}
// ResolveNextStage evaluates branch_rules from the current stage against
// accumulated stageData. Returns the target stage ordinal.
// Falls back to currentStage + 1 if no rules match or none are defined.
func ResolveNextStage(stages []models.WorkflowStage, currentStage int, stageData json.RawMessage) (int, error) {
if currentStage < 0 || currentStage >= len(stages) {
return currentStage + 1, nil
@@ -38,12 +44,12 @@ func ResolveNextStage(stages []models.WorkflowStage, currentStage int, stageData
stage := stages[currentStage]
var rules TransitionRulesWithConditions
if len(stage.TransitionRules) > 0 {
_ = json.Unmarshal(stage.TransitionRules, &rules)
var rules []Condition
if len(stage.BranchRules) > 0 && string(stage.BranchRules) != "[]" {
_ = json.Unmarshal(stage.BranchRules, &rules)
}
if len(rules.Conditions) == 0 {
if len(rules) == 0 {
return currentStage + 1, nil
}
@@ -55,7 +61,7 @@ func ResolveNextStage(stages []models.WorkflowStage, currentStage int, stageData
data = map[string]any{}
}
for _, cond := range rules.Conditions {
for _, cond := range rules {
if evaluateCondition(cond, data) {
target, err := resolveTarget(stages, cond.TargetStage)
if err != nil {