Feat v0.9.6 deprecate stage_type, collapse stage_mode

stage_type no longer validated — starlark_hook presence determines
automation. stage_mode collapsed from 4→3 values (form/delegated/
automated); "review" mapped to "form" on input for backward compat.
Migration 018 converts existing rows. Review surface removed (~110
lines). 4 package manifests updated.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-03 17:41:55 +00:00
parent 75d7abc089
commit 446edb8333
25 changed files with 121 additions and 224 deletions

View File

@@ -18,7 +18,7 @@ func testEngine(t *testing.T) *workflow.Engine {
return workflow.NewEngine(s, nil, nil)
}
// seedEngineFixture creates a 3-stage workflow (form → review → form),
// seedEngineFixture creates a 3-stage workflow (form → form → form),
// publishes it, and returns (workflowID, userID, teamID).
func seedEngineFixture(t *testing.T, slug string) (string, string, string) {
t.Helper()
@@ -42,7 +42,7 @@ func seedEngineFixture(t *testing.T, slug string) (string, string, string) {
stages := []models.WorkflowStage{
{WorkflowID: wf.ID, Ordinal: 0, Name: "intake", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple},
{WorkflowID: wf.ID, Ordinal: 1, Name: "review", StageMode: models.StageModeReview, Audience: models.AudienceTeam, StageType: models.StageTypeSimple, AssignmentTeamID: &teamID},
{WorkflowID: wf.ID, Ordinal: 1, Name: "review", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple, AssignmentTeamID: &teamID},
{WorkflowID: wf.ID, Ordinal: 2, Name: "final", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple},
}
for i := range stages {
@@ -134,8 +134,8 @@ func TestEngine_BranchRouting(t *testing.T) {
stages := []models.WorkflowStage{
{WorkflowID: wf.ID, Ordinal: 0, Name: "intake", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple, BranchRules: branchRules},
{WorkflowID: wf.ID, Ordinal: 1, Name: "normal-review", StageMode: models.StageModeReview, Audience: models.AudienceTeam, StageType: models.StageTypeSimple},
{WorkflowID: wf.ID, Ordinal: 2, Name: "escalation", StageMode: models.StageModeReview, Audience: models.AudienceTeam, StageType: models.StageTypeSimple},
{WorkflowID: wf.ID, Ordinal: 1, Name: "normal-review", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple},
{WorkflowID: wf.ID, Ordinal: 2, Name: "escalation", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple},
}
for i := range stages {
s.Workflows.CreateStage(ctx, &stages[i])
@@ -184,7 +184,7 @@ func TestEngine_PublicEntry(t *testing.T) {
stages := []models.WorkflowStage{
{WorkflowID: wf.ID, Ordinal: 0, Name: "public-form", StageMode: models.StageModeForm, Audience: models.AudiencePublic, StageType: models.StageTypeSimple},
{WorkflowID: wf.ID, Ordinal: 1, Name: "team-review", StageMode: models.StageModeReview, Audience: models.AudienceTeam, StageType: models.StageTypeSimple},
{WorkflowID: wf.ID, Ordinal: 1, Name: "team-review", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple},
}
for i := range stages {
s.Workflows.CreateStage(ctx, &stages[i])
@@ -253,7 +253,7 @@ func TestEngine_SignoffGate(t *testing.T) {
"validation": map[string]any{"required_approvals": 2},
})
stages := []models.WorkflowStage{
{WorkflowID: wf.ID, Ordinal: 0, Name: "gated-stage", StageMode: models.StageModeReview, Audience: models.AudienceTeam, StageType: models.StageTypeSimple, StageConfig: validationCfg},
{WorkflowID: wf.ID, Ordinal: 0, Name: "gated-stage", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple, StageConfig: validationCfg},
{WorkflowID: wf.ID, Ordinal: 1, Name: "done", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple},
}
for i := range stages {
@@ -307,7 +307,7 @@ func TestEngine_SignoffRejection(t *testing.T) {
"validation": map[string]any{"required_approvals": 1, "reject_action": "cancel"},
})
stages := []models.WorkflowStage{
{WorkflowID: wf.ID, Ordinal: 0, Name: "review", StageMode: models.StageModeReview, Audience: models.AudienceTeam, StageType: models.StageTypeSimple, StageConfig: validationCfg},
{WorkflowID: wf.ID, Ordinal: 0, Name: "review", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple, StageConfig: validationCfg},
{WorkflowID: wf.ID, Ordinal: 1, Name: "approved", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple},
}
for i := range stages {
@@ -590,7 +590,7 @@ func TestPackageInstall_ManifestRoundtrip(t *testing.T) {
{WorkflowID: wf.ID, Ordinal: 1, Name: "classify", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple, BranchRules: branchRules},
{WorkflowID: wf.ID, Ordinal: 2, Name: "fix-critical", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple, SLASeconds: &sla},
{WorkflowID: wf.ID, Ordinal: 3, Name: "fix-normal", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple},
{WorkflowID: wf.ID, Ordinal: 4, Name: "verify", StageMode: models.StageModeReview, Audience: models.AudienceTeam, StageType: models.StageTypeSimple},
{WorkflowID: wf.ID, Ordinal: 4, Name: "verify", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple},
}
for i := range stages {
if err := s.Workflows.CreateStage(ctx, &stages[i]); err != nil {