From 83b7173cf3526cfc7828bd33ae27ea1d4ede32e1 Mon Sep 17 00:00:00 2001 From: Henrique Costa Date: Mon, 27 Apr 2026 08:52:21 +0200 Subject: [PATCH 1/2] fix: refresh empty change-object actions Symptom: executing MDL that describes an empty change-object activity could create a model that fails Studio Pro validation with CE0032 because the action had no items and did not commit the object. Root cause: the MDL syntax represents an empty change action as `change $Object;`, but the builder always wrote RefreshInClient=false for change-object actions. For an empty non-committing change, Studio Pro requires the refresh flag to make the activity valid. Fix: set RefreshInClient=true when building a change-object action with no member changes. Change actions with explicit member assignments keep their previous refresh behavior. Tests: add a builder regression test that constructs an empty change statement and asserts that the resulting ChangeObjectAction refreshes in client. --- mdl/executor/bugfix_regression_test.go | 21 +++++++++++++++++++ .../cmd_microflows_builder_actions.go | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/mdl/executor/bugfix_regression_test.go b/mdl/executor/bugfix_regression_test.go index 0e1ed366..a3feef37 100644 --- a/mdl/executor/bugfix_regression_test.go +++ b/mdl/executor/bugfix_regression_test.go @@ -675,6 +675,27 @@ func TestCallMicroflowResultType_ResolvesSubsequentChangeMember(t *testing.T) { } } +func TestEmptyChangeObjectRefreshesInClient(t *testing.T) { + fb := &flowBuilder{posX: 100, posY: 100, spacing: HorizontalSpacing} + + id := fb.addChangeObjectAction(&ast.ChangeObjectStmt{Variable: "Object"}) + if id == "" || len(fb.objects) != 1 { + t.Fatalf("expected one change object activity, got id=%q objects=%d", id, len(fb.objects)) + } + + activity, ok := fb.objects[0].(*microflows.ActionActivity) + if !ok { + t.Fatalf("object type = %T, want *microflows.ActionActivity", fb.objects[0]) + } + action, ok := activity.Action.(*microflows.ChangeObjectAction) + if !ok { + t.Fatalf("action type = %T, want *microflows.ChangeObjectAction", activity.Action) + } + if !action.RefreshInClient { + t.Fatal("empty change object must refresh in client to remain valid without member changes or commit") + } +} + func TestCallMicroflowUnknownResultTypeStillDeclaresVariable(t *testing.T) { fb := &flowBuilder{ varTypes: map[string]string{"Result": "Old.ModuleEntity"}, diff --git a/mdl/executor/cmd_microflows_builder_actions.go b/mdl/executor/cmd_microflows_builder_actions.go index 9559eea2..213610cb 100644 --- a/mdl/executor/cmd_microflows_builder_actions.go +++ b/mdl/executor/cmd_microflows_builder_actions.go @@ -243,7 +243,7 @@ func (fb *flowBuilder) addChangeObjectAction(s *ast.ChangeObjectStmt) model.ID { BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())}, ChangeVariable: s.Variable, Commit: microflows.CommitTypeNo, - RefreshInClient: false, + RefreshInClient: len(s.Changes) == 0, } // Look up entity type from variable scope From 8d5cc94288e704e0ec73a608f433e6d96ae7e4c1 Mon Sep 17 00:00:00 2001 From: Henrique Costa Date: Mon, 27 Apr 2026 09:23:46 +0200 Subject: [PATCH 2/2] test: document empty change refresh validity Symptom: the empty change-object fix relies on RefreshInClient=true even though the CE0032 text mentions only missing items and commit state. Root cause: the Studio Pro validator accepts refresh-only empty changes, but that behavior is not obvious from public docs or the CE0032 wording. Fix: add an executable bug-test fixture for issue #339 and document the mx check behavior directly beside the builder inference. Tests: `./bin/mxcli check mdl-examples/bug-tests/339-empty-change-refresh-in-client.mdl` and `go test ./mdl/executor -run TestEmptyChangeObjectRefreshesInClient`. --- .../339-empty-change-refresh-in-client.mdl | 33 +++++++++++++++++++ .../cmd_microflows_builder_actions.go | 9 +++-- 2 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 mdl-examples/bug-tests/339-empty-change-refresh-in-client.mdl diff --git a/mdl-examples/bug-tests/339-empty-change-refresh-in-client.mdl b/mdl-examples/bug-tests/339-empty-change-refresh-in-client.mdl new file mode 100644 index 00000000..ee58b6a2 --- /dev/null +++ b/mdl-examples/bug-tests/339-empty-change-refresh-in-client.mdl @@ -0,0 +1,33 @@ +-- ============================================================================ +-- Bug #339: Empty change-object actions must refresh in client +-- ============================================================================ +-- +-- Symptom (before fix): +-- Rebuilding an empty change-object activity from MDL wrote a non-committing +-- ChangeObjectAction with no member changes and RefreshInClient=false. +-- Studio Pro / mx check rejected that activity with CE0032: +-- "Change action has no items specified and does not commit the object." +-- +-- After fix: +-- `change $Object;` is serialized as a refresh-only change action by setting +-- RefreshInClient=true. Although the CE0032 text mentions only items/commit, +-- empirical mx check validation accepts refresh as the third valid escape. +-- +-- Usage: +-- mxcli exec mdl-examples/bug-tests/339-empty-change-refresh-in-client.mdl -p app.mpr +-- mx check app.mpr +-- Expected: 0 new CE0032 errors for BugTest339.MF_RefreshOnlyChange. +-- ============================================================================ + +create module BugTest339; + +create entity BugTest339.Item ( + Name: String(100) +); + +create microflow BugTest339.MF_RefreshOnlyChange () +begin + $Item = create BugTest339.Item; + change $Item; +end; +/ diff --git a/mdl/executor/cmd_microflows_builder_actions.go b/mdl/executor/cmd_microflows_builder_actions.go index 213610cb..5605b12e 100644 --- a/mdl/executor/cmd_microflows_builder_actions.go +++ b/mdl/executor/cmd_microflows_builder_actions.go @@ -240,9 +240,12 @@ func (fb *flowBuilder) addRollbackAction(s *ast.RollbackStmt) model.ID { // addChangeObjectAction creates a CHANGE statement. func (fb *flowBuilder) addChangeObjectAction(s *ast.ChangeObjectStmt) model.ID { action := µflows.ChangeObjectAction{ - BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())}, - ChangeVariable: s.Variable, - Commit: microflows.CommitTypeNo, + BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())}, + ChangeVariable: s.Variable, + Commit: microflows.CommitTypeNo, + // Studio Pro rejects an empty non-committing change action unless it + // refreshes in client. The CE0032 message mentions only items/commit, + // but mx check accepts RefreshInClient=true as the third valid escape. RefreshInClient: len(s.Changes) == 0, }