From 801b7c1cb3636db98167219bb0a0acae6c528e4d Mon Sep 17 00:00:00 2001 From: Henrique Costa Date: Sun, 26 Apr 2026 00:02:44 +0200 Subject: [PATCH 1/2] fix: skip reference checks for excluded microflows Studio Pro allows excluded documents to keep stale references because they are not part of the runnable app. The reference validator still walked excluded microflow bodies and reported missing microflow, page, or java-action references. That made project audits fail on intentionally excluded legacy scaffolding instead of actionable included-model errors. Skip reference collection for excluded `CreateMicroflowStmt` values while keeping included microflows validated normally. Tests cover both sides: excluded missing references are ignored, and included missing references still report errors. --- mdl/executor/bugfix_regression_test.go | 65 ++++++++++++++++++++++++++ mdl/executor/validate.go | 6 +++ 2 files changed, 71 insertions(+) diff --git a/mdl/executor/bugfix_regression_test.go b/mdl/executor/bugfix_regression_test.go index 0e1ed366..563d542c 100644 --- a/mdl/executor/bugfix_regression_test.go +++ b/mdl/executor/bugfix_regression_test.go @@ -696,3 +696,68 @@ func TestCallMicroflowUnknownResultTypeStillDeclaresVariable(t *testing.T) { t.Fatal("expected Result to remain declared after unresolved call return type") } } + +func TestValidateMicroflowReferencesSkipsExcludedMicroflow(t *testing.T) { + moduleID := model.ID("module-1") + backend := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListModulesFunc: func() ([]*model.Module, error) { + return []*model.Module{{ + BaseElement: model.BaseElement{ID: moduleID}, + Name: "SyntheticAudit", + }}, nil + }, + ListMicroflowsFunc: func() ([]*microflows.Microflow, error) { + return nil, nil + }, + } + ctx, _ := newMockCtx(t, withBackend(backend)) + + stmt := &ast.CreateMicroflowStmt{ + Excluded: true, + Name: ast.QualifiedName{Module: "SyntheticAudit", Name: "ExcludedLegacyFlow"}, + Body: []ast.MicroflowStatement{ + &ast.CallMicroflowStmt{ + MicroflowName: ast.QualifiedName{Module: "SyntheticAudit", Name: "DeletedScaffoldFlow"}, + }, + }, + } + + if err := validate(ctx, stmt); err != nil { + t.Fatalf("excluded microflow reference validation returned error: %v", err) + } +} + +func TestValidateMicroflowReferencesReportsIncludedMissingMicroflow(t *testing.T) { + moduleID := model.ID("module-1") + backend := &mock.MockBackend{ + IsConnectedFunc: func() bool { return true }, + ListModulesFunc: func() ([]*model.Module, error) { + return []*model.Module{{ + BaseElement: model.BaseElement{ID: moduleID}, + Name: "SyntheticAudit", + }}, nil + }, + ListMicroflowsFunc: func() ([]*microflows.Microflow, error) { + return nil, nil + }, + } + ctx, _ := newMockCtx(t, withBackend(backend)) + + stmt := &ast.CreateMicroflowStmt{ + Name: ast.QualifiedName{Module: "SyntheticAudit", Name: "IncludedFlow"}, + Body: []ast.MicroflowStatement{ + &ast.CallMicroflowStmt{ + MicroflowName: ast.QualifiedName{Module: "SyntheticAudit", Name: "DeletedScaffoldFlow"}, + }, + }, + } + + err := validate(ctx, stmt) + if err == nil { + t.Fatal("expected missing microflow reference error") + } + if !strings.Contains(err.Error(), "microflow not found: SyntheticAudit.DeletedScaffoldFlow") { + t.Fatalf("unexpected validation error: %v", err) + } +} diff --git a/mdl/executor/validate.go b/mdl/executor/validate.go index 88f52f49..8621e2db 100644 --- a/mdl/executor/validate.go +++ b/mdl/executor/validate.go @@ -396,6 +396,12 @@ func validateMicroflowReferences(ctx *ExecContext, s *ast.CreateMicroflowStmt, s if !ctx.Connected() || len(s.Body) == 0 { return nil } + if s.Excluded { + // Studio Pro allows excluded documents to keep stale references. Reference + // checks should not fail a roundtrip audit for microflows that are not part + // of the runnable app. + return nil + } // Collect all references from the microflow body refs := µflowRefCollector{} From 8a29914644c6cb138efa03ec4104b2f8315e07de Mon Sep 17 00:00:00 2001 From: Henrique Costa Date: Sun, 26 Apr 2026 23:58:02 +0200 Subject: [PATCH 2/2] test: add bug-test reproducer for excluded-microflow reference validation Adds an MDL script under mdl-examples/bug-tests/ pairing an excluded microflow with a broken call against an included microflow with a valid call. `mxcli check --references` must pass; removing `@excluded` makes the same script surface the broken call as an error. Co-Authored-By: Claude Opus 4.7 --- .../312-validate-skip-excluded-microflows.mdl | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 mdl-examples/bug-tests/312-validate-skip-excluded-microflows.mdl diff --git a/mdl-examples/bug-tests/312-validate-skip-excluded-microflows.mdl b/mdl-examples/bug-tests/312-validate-skip-excluded-microflows.mdl new file mode 100644 index 00000000..c15bacbd --- /dev/null +++ b/mdl-examples/bug-tests/312-validate-skip-excluded-microflows.mdl @@ -0,0 +1,63 @@ +-- ============================================================================ +-- Bug #312: Reference validation flagged broken calls inside excluded microflows +-- ============================================================================ +-- +-- Symptom (before fix): +-- `mxcli check --references` walked every microflow body — including ones +-- marked `@excluded` — and reported missing microflow / page / java-action +-- references in them. Excluded documents are not part of the build, and +-- Studio Pro tolerates dangling references in them, so this produced +-- false positives during agentic workflows that legitimately stash +-- broken intermediate state inside excluded scaffolding. +-- +-- Root cause: +-- The validator collected references from every CreateMicroflowStmt +-- without checking the Excluded flag. +-- +-- After fix: +-- `validate.go` skips reference collection for excluded microflows. +-- Included microflows are still validated normally, so the negative +-- case (typo in a real call) still fails the check. +-- +-- Usage: +-- mxcli check --references mdl-examples/bug-tests/312-validate-skip-excluded-microflows.mdl -p app.mpr +-- The check must succeed: the excluded microflow's broken call to +-- BugTest312.NoSuchTarget is ignored. +-- +-- To verify the negative case still triggers, remove `@excluded` from +-- the second microflow and re-run — `mxcli check --references` should +-- then report the missing reference. +-- ============================================================================ + +create module BugTest312; + +create microflow BugTest312.MF_RealTarget () +returns string as $msg +begin + return 'real target'; +end; +/ + +-- Excluded scaffolding that contains a broken call. mxcli check --references +-- must NOT report this as an error. +@excluded +create microflow BugTest312.MF_ExcludedWithBrokenCall () +returns string as $msg +begin + declare $msg string = empty; + $msg = call microflow BugTest312.NoSuchTarget(); + return $msg; +end; +/ + +-- Negative control: an INCLUDED microflow whose call is valid. Together with +-- the excluded one above, the check confirms the validator still walks +-- non-excluded microflows. +create microflow BugTest312.MF_IncludedValid () +returns string as $msg +begin + declare $msg string = empty; + $msg = call microflow BugTest312.MF_RealTarget(); + return $msg; +end; +/