Skip to content

fix: preserve validation feedback targets#359

Merged
ako merged 9 commits intomendixlabs:mainfrom
hjotha:submit/validation-feedback-targets
May 2, 2026
Merged

fix: preserve validation feedback targets#359
ako merged 9 commits intomendixlabs:mainfrom
hjotha:submit/validation-feedback-targets

Conversation

@hjotha
Copy link
Copy Markdown
Contributor

@hjotha hjotha commented Apr 27, 2026

Summary

Fixes validation feedback round-trips for targets that are not simple attributes.

Changes

  • Accept object-only validation feedback targets such as $Object in MDL.
  • Preserve qualified association targets such as $Object/Module.Object_Association as a single path segment.
  • Emit AssociationName when a validation feedback action does not have an AttributeName.

Validation

  • go test ./mdl/executor -run TestFormatAction_ValidationFeedback
  • go test ./mdl/visitor -run TestValidationFeedback
  • make build
  • make lint-go
  • make test

Closes #358
Part of #332

hjotha pushed a commit to hjotha/mxcli that referenced this pull request Apr 27, 2026
…tion

Adds an MDL script under mdl-examples/bug-tests/ exercising
attribute-target validation feedback. The script header documents
that PR mendixlabs#359 fixes only the parser+formatter; object-only and
association-target builder support is intentionally out of scope.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This was referenced Apr 28, 2026
hjotha pushed a commit to hjotha/mxcli that referenced this pull request Apr 30, 2026
…tion

Adds an MDL script under mdl-examples/bug-tests/ exercising
attribute-target validation feedback. The script header documents
that PR mendixlabs#359 fixes only the parser+formatter; object-only and
association-target builder support is intentionally out of scope.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@hjotha hjotha force-pushed the submit/validation-feedback-targets branch from f63e3c4 to f59fd39 Compare April 30, 2026 08:13
Copy link
Copy Markdown
Collaborator

@ako ako left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — fix: preserve validation feedback targets

Overview

Well-scoped fix. The three-part approach (grammar → visitor → formatter) is correct, the scope note in the bug test explicitly documents what remains out of scope (builder producing CE0091 for object-only authored targets), and the formatter change is minimal. Tests cover the new grammar forms, the builder paths, and the formatter paths.


Moderate: attributePath grammar change is broader than its context suggests

The diff changes:

 attributePath
-    : VARIABLE ((SLASH | DOT) (IDENTIFIER | qualifiedName))+
+    : VARIABLE ((SLASH | DOT) qualifiedName)+

attributePath is used in five places beyond validationFeedbackStatement:

  • setStatement (SET $var/Attr = expr)
  • loopStatement (LOOP $item IN $list/assoc)
  • aggregateExpression (SUM($list/attr), AVERAGE, MINIMUM, MAXIMUM)

The change is backward-compatible — a bare IDENTIFIER is still a valid qualifiedName — but none of these other usages have regression tests that exercise the grammar-change path. A $Order/SUM or $List/Module.Attr through the SET or LOOP paths should still parse identically; worth a comment in the grammar noting that the rule is shared and the change is intentional.


Moderate: old two-segment association path (len(segs) >= 2) is now dead for associations

Under the old grammar, $Obj/Module.Association produced two segments ("Module" sep /, "Association" sep .), which is why the code had:

} else if len(segs) >= 2 && segs[0].Separator == "/" && segs[1].Separator == "." {
    // Reconstruct "Module.AssociationName" from segments

Under the new grammar qualifiedName absorbs the dot, so $Obj/Module.Association now produces one segment "Module.Association" — handled correctly by the new case 1 path. The len(segs) >= 2 branch is now only reachable for multi-hop paths like $Obj/Part1/Part2, where its comment ("association qualified name") is now misleading. The reconstruction logic (strings.Join(parts, ".")) would incorrectly join multi-hop segments into an association name in that case.

The old code path should either be removed (since the grammar change makes it unreachable for single associations) or its comment and logic updated to reflect that it now only handles multi-hop traversal paths.


Minor: redundant else if in multi-segment block

} else if entityQName == "" && strings.Count(segs[0].Name, ".") == 1 {
    associationName = segs[0].Name

This branch sits inside the len(segs) >= 2 block, so it applies when: there are 2+ segments, the path doesn't match the / then . separator pattern, entityQName is empty, and the first segment has exactly one dot. Under the new grammar this combination is hard to construct in a well-formed microflow — a description of when this fires would help.


Positive

  • Grammar fix is correct: qualifiedName subsumes bare identifiers so all existing attributePath syntax still parses.
  • validationFeedbackStatement accepting bare VARIABLE is the right grammar shape for the object-only target.
  • Dot-count heuristic for single segments (0 → attribute, 1 → association, 2+ → qualified attribute) is consistent with resolveMemberChangeFallback elsewhere.
  • Scope note in the bug test is honest and specific — the CE0091 gap is documented, not hidden.

@ako ako added this to the v0.8.0 milestone Apr 30, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 1, 2026

AI Code Review

Critical Issues

  • None found

Moderate Issues

  • None found

Minor Issues

  • None found

What Looks Good

  • The PR correctly addresses validation feedback round-trips for non-simple attribute targets
  • Grammar changes are minimal and focused, allowing both attribute paths and bare variables
  • Builder and formatter logic are updated consistently to handle object-only and association targets
  • Comprehensive test coverage added including unit tests and formatter tests
  • Bug test file documents the issue and verification steps clearly
  • Solution preserves existing behavior while extending to new cases
  • PR correctly notes builder limitations are out of scope (Attribute = "" issue)
  • Changes follow MDL design guidelines (qualified names, readable syntax)

Recommendation

Approve the PR. The changes are well-structured, address the issue comprehensively, include appropriate tests, and follow project guidelines. The generated parser interp file modification is expected when the grammar changes.


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 1, 2026

AI Code Review

What Looks Good

  • The PR correctly addresses issue Preserve validation feedback object and association targets #358 by fixing validation feedback round-trips for non-attribute targets
  • Comprehensive test coverage including:
    • A dedicated bug test file (mdl-examples/bug-tests/358-validation-feedback-targets.mdl) that verifies the fix
    • Unit tests for the builder logic (cmd_microflows_builder_validation_feedback_test.go)
    • Unit tests for the formatter (cmd_microflows_format_action_test.go)
  • The grammar change is minimal and precise, extending validationFeedbackStatement to accept VARIABLE in addition to attributePath
  • Executor logic was refactored for clarity, properly handling:
    • Object-only targets (zero dots in first segment)
    • Qualified association targets (one dot in first segment)
    • Qualified attribute targets (two+ dots in first segment)
    • Edge cases like empty entityQName and multi-hop paths
  • The solution preserves existing behavior for attribute targets while adding support for object-only and association targets
  • Clear documentation in the test file explaining what's in scope (parser/formatter fixes) vs. out of scope (builder changes for Studio Pro validity)

Recommendation

The PR is well-scoped, thoroughly tested, and correctly implements the fix for validation feedback round-trips. It follows the project's established patterns and maintains backward compatibility.

Approve this PR.


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

hjotha pushed a commit to hjotha/mxcli that referenced this pull request May 2, 2026
…tion

Adds an MDL script under mdl-examples/bug-tests/ exercising
attribute-target validation feedback. The script header documents
that PR mendixlabs#359 fixes only the parser+formatter; object-only and
association-target builder support is intentionally out of scope.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@hjotha hjotha force-pushed the submit/validation-feedback-targets branch from 73138b8 to 248a1f1 Compare May 2, 2026 08:20
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 2, 2026

AI Code Review

Review Summary

This PR fixes validation feedback round-trips for non-simple attribute targets in MDL microflows. It properly handles object-only targets ($Object) and qualified association targets ($Object/Module.Association) by updating the grammar, executor builder, and formatter.

What Looks Good

  • Well-scoped fix: Addresses exactly the issue described in Preserve validation feedback object and association targets #358 without scope creep
  • Comprehensive testing: Adds bug test file, unit tests for builder logic, and formatter tests
  • Clear documentation: The bug test file includes detailed symptom/scope notes and verification steps
  • Correct grammar modifications:
    • Allows VARIABLE as validation feedback target (for object-only)
    • Treats qualified names as single segments (preserves Module.Association as one unit)
  • Logical executor implementation:
    • Properly distinguishes 0 dots (attribute), 1 dot (association), 2+ dots (qualified attribute)
    • Handles edge cases like missing variable types
  • Formatter improvements:
    • Emits AssociationName when AttributeName is absent
    • Correctly formats object-only and association targets

Minor Issues

  • Parser regeneration verification: The diff shows changes to MDLParser.interp (interpreter file) but doesn't show the actual generated Go parser files being committed. Per checklist: "Parser regenerated — make grammar run, generated files committed". Need to verify make grammar was run and generated files committed.
  • AST consideration: No explicit AST node changes shown, but given the visitor/executor handle the new syntax correctly, existing AST nodes may suffice. Should confirm if AST changes were needed for this grammar modification.

Recommendation

Approve with the following note:
Please verify that make grammar was executed and the generated parser files in mdl/grammar/parser/ were properly committed (though noted as noise, they should be part of the PR). The interpreter file changes indicate regeneration occurred, but we need to confirm the Go files were included.

The fix is technically sound, well-tested, and follows MDL design principles. It enables proper round-tripping of validation feedback statements for object and association targets as intended.


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

hjothamendix and others added 7 commits May 2, 2026 12:00
Symptom: all open PRs against main failed the shared build-and-test job in
make test-integration, even when their local build/test/lint validation passed.
The failures reproduced on origin/main, so they were baseline CI instability
rather than PR-specific regressions.

Root cause: TestWatcherDebounce could allow stale timer callbacks to send an
extra message under slow scheduling, nanoflow integration fixtures used MDL
syntax that no longer matched the grammar, and the doctype mx-check harness did
not classify known page/nanoflow showcase consistency errors as expected
limitations.

Fix: guard watcher debounce callbacks with a generation counter, tighten the
watcher burst test, update nanoflow fixtures to current MDL syntax, and extend
the known consistency-error allowlist for showcase-only limitations.

Tests: make build
Tests: go test ./cmd/mxcli/tui -run TestWatcherDebounce -count=20 -v
Tests: ./bin/mxcli check mdl-examples/doctype-tests/02b-nanoflow-examples.mdl
Tests: go test -tags integration -count=1 -timeout 30m ./mdl/executor -run 'TestRoundtripNanoflow_(Loop|EnumParameter|Annotations)|TestMxCheck_DoctypeScripts/02b-nanoflow-examples.mdl|TestMxCheck_DoctypeScripts/03-page-examples.mdl' -v
Tests: make test
Tests: make lint-go
Tests: make test-integration
Validation feedback actions can target an object or an association path, but the MDL grammar only accepted attribute paths and the formatter only emitted attribute targets. Round-tripping those actions either failed to parse object-only targets or dropped the association name.

The parser now accepts object-only validation feedback targets and treats each path segment as a qualified name so Module.Association stays a single association segment. The formatter emits AssociationName when AttributeName is absent, preserving association targets from existing models.

Tests cover object-only and association formatter output, parser coverage for both target shapes, and the full build/lint/test validation passed locally.
…tion

Adds an MDL script under mdl-examples/bug-tests/ exercising
attribute-target validation feedback. The script header documents
that PR mendixlabs#359 fixes only the parser+formatter; object-only and
association-target builder support is intentionally out of scope.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Symptom: validation feedback targeting $Object/Module.Association could be
written as an invalid Attribute value after roundtrip.

Root cause: the builder treated a single parsed segment containing one dot as a
direct attribute and prefixed the object entity, producing
Module.Entity.Module.Association.

Fix: apply the same member-name shape rule used elsewhere: bare names are
attributes, one-dot names are associations, and two-or-more-dot names are fully
qualified attributes.

Tests: add synthetic builder coverage for qualified association and qualified
attribute validation feedback targets.
Symptom: the validation feedback path parser now keeps qualified segments
intact, but the builder still carried an obsolete two-segment association
fallback from the older grammar shape.

Root cause: `$Object/Module.Association` is now represented as one slash
segment, so the old slash-then-dot branch was dead for associations and
misleading for future multi-hop paths.

Fix: document attributePath as a shared qualified segment rule and remove
the obsolete reconstruction path from validation feedback member resolution.

Tests: rerun focused executor/visitor/grammar tests plus make build and
make test.
The validation feedback target grammar now uses qualifiedName segments so
Module.Association remains a single slash segment. The previous fallback still
contained a stale multi-segment association path branch from the old grammar,
which could join unrelated traversal pieces into an association name.

This removes the stale branch, documents that attributePath is shared by other
microflow statements, and adds synthetic parser coverage for a SET target with
a slash-qualified segment.

Tests:
- go test ./mdl/visitor ./mdl/executor -run 'TestValidationFeedback|TestSharedAttributePathKeepsQualifiedSlashSegment|TestAddValidationFeedback|TestFormatAction_ValidationFeedback' -count=1
- make build
- make test
@hjotha hjotha force-pushed the submit/validation-feedback-targets branch from 248a1f1 to 2132acd Compare May 2, 2026 10:03
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 2, 2026

AI Code Review

We are reviewing a pull request (PR) that is titled "fix: preserve validation feedback targets". The PR body indicates it fixes validation feedback round-trips for targets that are not simple attributes.

The changes in the PR are:

  • Accept object-only validation feedback targets such as $Object in MDL.
  • Preserve qualified association targets such as $Object/Module.Object_Association as a single path segment.
  • Emit AssociationName when a validation feedback action does not have an AttributeName.

We are to review against the checklist in CLAUDE.md, focusing on:

  • Overlap & duplication
  • MDL syntax design (if applicable)
  • Full-stack consistency (for MDL features)
  • Test coverage
  • Security & robustness
  • Scope & atomicity
  • Code quality
  • Bugs & correctness

Let's break down the changes:

  1. The PR modifies the TUI watcher (not directly related to the MDL feature but for debouncing in the TUI) and its test.
  2. It adds a bug test for the validation feedback targets (in mdl-examples/bug-tests/358-validation-feedback-targets.mdl).
  3. It modifies the nanoflow examples doctype test file (02b-nanoflow-examples.mdl) by simplifying it (removing many examples and leaving only a few). However, note that the PR description says it's part of Microflow roundtrip stabilization for large-project audit findings #332 and closes Preserve validation feedback object and association targets #358. The change to the doctest file might be unrelated? But we must check if it's necessary.
  4. It modifies the microflow builder calls (in mdl/executor/cmd_microflows_builder_calls.go) to change how validation feedback targets are processed.
  5. It adds a test for the validation feedback action in the builder (in mdl/executor/cmd_microflows_builder_validation_feedback_test.go).
  6. It modifies the formatter for validation feedback actions (in mdl/executor/cmd_microflows_format_action.go) and adds tests for it (in mdl/executor/cmd_microflows_format_action_test.go).
  7. It updates the roundtrip doctype test to ignore certain CE errors for the nanoflow examples (in mdl/executor/roundtrip_doctype_test.go and mdl/executor/roundtrip_nanoflow_test.go).
  8. It modifies the MDL grammar (in mdl/grammar/MDLParser.g4) to allow VARIABLE (i.e., object-only) as a target for validation feedback, in addition to attributePath.

Now, let's check against the checklist:

Overlap & duplication

  • The PR is about fixing validation feedback targets. We should check if there is existing code that does the same.
  • The changes are in the executor for microflows, specifically for validation feedback actions. We don't see obvious duplication.
  • The bug test is new and specific to issue Preserve validation feedback object and association targets #358.
  • The change to the nanoflow examples doctest file (02b-nanoflow-examples.mdl) is a reduction. We must check if this is necessary or if it's removing existing test coverage. However, note that the PR says it's part of Microflow roundtrip stabilization for large-project audit findings #332 (which might be a refactoring of the doctests). Since the PR is about a bug fix, and the change to the doctest file is not directly related, we should check if it's justified. But note: the PR description does not mention changing the doctest examples. However, the change in the doctest file is to simplify it (removing many examples) and then adding a few back. This might be part of a separate effort (maybe Microflow roundtrip stabilization for large-project audit findings #332) to clean up the doctests. Since the PR is part of Microflow roundtrip stabilization for large-project audit findings #332, it might be acceptable. However, we are to check for overlap and duplication: removing examples might reduce test coverage, but the PR adds a specific bug test. We'll note that the doctest change is not directly related to the bug fix but is part of the PR.

MDL syntax design

  • The PR modifies the grammar to allow VARIABLE (i.e., $Object) as a target for validation feedback, in addition to the existing attributePath.
  • The change in the grammar is:
    validationFeedbackStatement
  • : VALIDATION FEEDBACK attributePath MESSAGE expression (OBJECTS LBRACKET expressionList RBRACKET)?
  • : VALIDATION FEEDBACK (attributePath | VARIABLE) MESSAGE expression (OBJECTS LBRACKET expressionList RBRACKET)?
    ```
  • This change allows a validation feedback statement to target a variable (object) directly, e.g., validation feedback $Customer message '...';.

  • We must check if this follows the MDL syntax design guidelines:

    • Uses standard CRUD verbs: The verb is validation feedback (which is not a CRUD verb, but note that validation feedback is a statement in microflows/nanoflows, not a schema alteration). The guidelines for MDL syntax design in the checklist are for new or modified MDL syntax. The checklist item says: "New or modified MDL syntax must follow the design guidelines". However, note that the validation feedback statement is not a CRUD statement (like CREATE, ALTER, etc.) but a microflow/nanoflow statement. The design guidelines in the checklist are general for MDL syntax, but the specific CRUD verbs might not apply to microflow statements. However, the guideline says: "Follows standard patterns — uses create/alter/drop/show/describe, not custom verbs". This is likely referring to the top-level statements (like CREATE ENTITY, etc.). For microflow statements, we have to look at existing patterns.

    Looking at existing microflow statements:

    • create object $Variable of Type;
    • change $Variable (property = value);
    • delete $Variable;
    • show message 'text';
    • validation feedback $Variable/Attribute message 'text';

    The validation feedback statement is already using a verb phrase "validation feedback", which is consistent with other microflow statements (like "show message", "log message", etc.). So the verb is not a custom verb in the sense of being non-standard for microflows.

    • Qualified names: The change allows $Object (which is a variable, not a qualified name) and $Object/Attribute (which is a path). The variable is not a qualified name, but in the context of a microflow, variables are allowed. The grammar for variable is already defined elsewhere (as VARIABLE). The change does not introduce bare names in places where qualified names are expected (like in entity names). So it's acceptable.

    • Property format: Not applicable because validation feedback is a statement that takes a target and a message, not a property list.

    • Colon for property definitions, AS for name mappings: Not applicable.

    • Keywords are full English words: "validation feedback" is two words, but note that the grammar treats it as two tokens: VALIDATION and FEEDBACK. This is consistent with other multi-word statements (like "show message").

    • Statement reads as English: "validation feedback $Customer message 'Email is required'" reads as English.

    • No keyword overloading: The words VALIDATION and FEEDBACK are not used elsewhere in a conflicting way? We should check if they are used as other tokens. But given the context, it's unlikely.

    • Diff-friendly: Adding the object-only target is a one-line change in the grammar? Actually, the change is in the grammar rule, but it's a small change.

    • Consistent with existing MDL patterns: The existing validation feedback statement used an attribute path. Now we are allowing a variable (object) as well. This is consistent because the variable is already used in other statements (like change $Variable).

Full-stack consistency

  • The PR modifies:

    • Grammar: MDLParser.g4 (added VARIABLE as an alternative for the target)
    • AST: We don't see an explicit AST change, but note that the ValidationFeedbackStmt in the AST already had an AttributePath field. Now, we are allowing the target to be either an attribute path or a variable. However, looking at the AST definition (not shown in the diff, but we can infer), the ValidationFeedbackStmt likely has an AttributePath field that is now used for both the attribute path and the variable? Or did they change the AST?

    Let's look at the changes in the executor:

    In cmd_microflows_builder_calls.go, the function addValidationFeedbackAction now handles the AttributePath differently. It uses the Segments field of the AttributePathExpr to determine if it's an object-only target (one segment with a slash? Actually, note the change in the grammar: the attributePath rule was changed to allow VARIABLE as an alternative. But wait, the grammar change also modified the attributePath rule? Actually, no:

    The grammar change was:

    validationFeedbackStatement
  • : VALIDATION FEEDBACK attributePath MESSAGE expression (OBJECTS LBRACKET expressionList RBRACKET)?

  • : VALIDATION FEEDBACK (attributePath | VARIABLE) MESSAGE expression (OBJECTS LBRACKET expressionList RBRACKET)?

And separately, the `attributePath` rule was changed earlier? Actually, we see:

```diff
// Shared by SET, LOOP, aggregate expressions, and validation feedback targets.
// The segment is a qualifiedName so `$Obj/Module.Association` remains a
// single slash segment rather than being split at the dot.
attributePath
-    : VARIABLE ((SLASH | DOT) (IDENTIFIER | qualifiedName))+
+    : VARIABLE ((SLASH | DOT) qualifiedName)+

This change to attributePath is to make sure that a segment like $Obj/Module.Association is kept as one segment (so that the dot in Module.Association is not treated as a separator). This change is unrelated to the validation feedback object-only target, but it is necessary for preserving qualified association targets.

Now, the AST for AttributePathExpr (which is used in the validation feedback statement) is defined in mdl/ast/. We don't see a change in the AST in the diff, so we assume the AST node AttributePathExpr is still used. However, note that the validation feedback statement now can have a VARIABLE (which is not an attributePath). How is that represented?

Looking at the change in validationFeedbackStatement: it now allows either attributePath or VARIABLE. This means the AST for the validation feedback statement must now have a field that can be either an AttributePathExpr or a VariableExpr (or similar). However, we don't see a change in the AST in the diff. This is a potential issue.

Let me check the diff again: we don't see any change in mdl/ast/. The PR only shows changes in the files listed. Therefore, the AST must have been left unchanged? That would be a problem because the AST node for ValidationFeedbackStmt likely only has an AttributePath field.

However, looking at the change in the executor (cmd_microflows_builder_calls.go), we see:

// Build attribute or association name from variable type and attribute path.
// The current grammar keeps `$Product/Module.Association` as one slash
// segment, so dot count on that segment is the disambiguator:
//   0 dots: attribute relative to the target entity.
//   1 dot: association qualified by module.
//   2+ dots: fully qualified attribute.
var attributeName string
var associationName string
if entityQName, ok := fb.varTypes[s.AttributePath.Variable]; ok && len(s.AttributePath.Segments) > 0 {

This code is still using s.AttributePath (which is of type *ast.AttributePathExpr). But note: the grammar change allows the validation feedback statement to have a VARIABLE (which is not an attributePath). How is that represented in the AST?

Actually, the attributePath rule in the grammar is used for the attributePath alternative, and the VARIABLE alternative is a separate token. The AST must have been changed to accommodate this, but we don't see it in the diff. This is a red flag.

However, note that the PR might have omitted the AST change because it was done in a previous commit? But we are reviewing this PR as a whole.

Alternatively, let's look at the ValidationFeedbackStmt in the AST. Since we don't have the AST file in the diff, we must assume it was not changed. But then how does the AST represent a variable-only target?

Looking at the change in the executor test file (cmd_microflows_builder_validation_feedback_test.go), we see:

fb.addValidationFeedbackAction(&ast.ValidationFeedbackStmt{
    AttributePath: &ast.AttributePathExpr{
        Variable: "FormObject",
        Segments: []ast.PathSegment{
            {Name: "Synthetic.FormObject_Target", Separator: "/"},
        },
    },
    Message: &ast.LiteralExpr{Kind: ast.LiteralString, Value: "Select a target"},
})

And for the object-only target in the formatter test:

action := &microflows.ValidationFeedbackAction{
    ObjectVariable: "Customer",
    Template: &model.Text{
        Translations: map[string]string{"en_US": "Select a customer"},
    },
}

This suggests that the AST for ValidationFeedbackStmt might have been changed to include an ObjectVariable field? But we don't see that in the diff.

Alternatively, note that the AttributePathExpr might be able to represent a variable-only target by having zero segments? But the grammar for attributePath requires at least one segment (because of the + in the rule). The VARIABLE alternative in the validation feedback statement is a separate token, so the AST must have a way to distinguish.

Without seeing the AST change, we cannot verify full-stack consistency. However, note that the PR does not show any change in the AST directory. This is a potential gap.

But wait: the PR description says "Fixes validation feedback round-trips for targets that are not simple attributes." and the changes include the grammar and the executor. The AST might have been changed in a commit that is not shown in the diff? However, we are to review the diff as provided.

Since the diff does not show any change in mdl/ast/, we must assume that the AST was not changed. This would mean that the AST node for ValidationFeedbackStmt still only has an AttributePath field. How then is the variable-only target represented?

Looking at the grammar change: the validation feedback statement can now be:
VALIDATION FEEDBACK VARIABLE MESSAGE expression ...

In the AST, this would have to be represented by setting the AttributePath field to something that represents just the variable. But the AttributePathExpr requires at least one segment (as per the grammar rule for attributePath). However, note that the attributePath rule was changed to:
: VARIABLE ((SLASH | DOT) qualifiedName)+
This requires at least one segment after the variable (because of the +). So a standalone variable would not match the attributePath rule.

Therefore, the AST must have been changed to allow the ValidationFeedbackStmt to have either an AttributePathExpr or a VariableExpr (or perhaps the AttributePathExpr was made to allow zero segments?).

Since we don't see the AST change, we have to flag this as a potential issue: the AST might not be updated to reflect the grammar change, leading to a mismatch.

However, note that the PR includes a test for the builder that uses an AttributePathExpr for the object-only target? Actually, no: in the test for the builder, we see:

fb.addValidationFeedbackAction(&ast.ValidationFeedbackStmt{
    AttributePath: &ast.AttributePathExpr{
        Variable: "FormObject",
        Segments: []ast.PathSegment{
            {Name: "Synthetic.FormObject_Target", Separator: "/"},
        },
    },
    ...
})

This is for a qualified association target (one segment with a slash). For the object-only target, we see in the formatter test:

action := &microflows.ValidationFeedbackAction{
    ObjectVariable: "Customer",
    ...
}

How does the builder create an action with ObjectVariable set? It must be coming from the AST. Therefore, the AST must have been changed to include an ObjectVariable field in ValidationFeedbackStmt when the target is a variable.

Since we don't see the AST change in the diff, we must conclude that the AST change is missing from the diff we are reviewing. This is a problem for full-stack consistency.

However, note that the PR might be part of a series and the AST change was in a previous commit? But we are reviewing this PR as a whole.

Given the instructions, we must review the diff as provided. The diff does not show any AST change. Therefore, we cannot verify that the AST was updated. This is a gap.

But wait: the PR also modifies the attributePath rule (to keep qualified names as one segment). This change is in the grammar and might affect the AST? The AttributePathExpr AST node likely still has the same structure (Variable and Segments). The change in the grammar for attributePath is internal to how the rule is parsed, but the AST node might remain the same.

For the validation feedback statement, the grammar now has two alternatives. The AST must reflect that. Without seeing the AST change, we cannot be sure.

However, note that the PR includes a test for the builder that uses an AttributePathExpr for the association target and the formatter test that uses an action with ObjectVariable set. This suggests that the AST change might have been made but not included in the diff we are looking at? Or perhaps the AST node was already designed to handle both?

Let me look at the existing AST for ValidationFeedbackStmt (from memory or logic): it likely had:
Variable string
AttributePath


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

@hjotha
Copy link
Copy Markdown
Contributor Author

hjotha commented May 2, 2026

CI stabilization note: merged #412 into this branch so it uses the same integration-CI baseline as the rest of the active PR set.

Symptom: CI failed in TestMxCheck_DoctypeScripts/empty_java_action_argument.mdl because a Java action declared as RETURNS Void was written as an entity return type named .void, and Studio Pro reported CE1613.

Root cause: the generic data-type visitor treats bare qualified names as entity/enumeration references. Java action return types reused that generic path, so the explicit Void spelling became a qualified name instead of ast.TypeVoid.

Fix: add a Java-action return-type wrapper that maps unqualified Void to ast.TypeVoid while leaving generic data-type parsing unchanged for parameters and attributes.

Tests: added visitor coverage for explicit RETURNS Void; verified mxcli check for the doctype fixture and the targeted integration subtest that failed in GitHub Actions.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 2, 2026

AI Code Review

Let me review this pull request based on the checklist provided in the CLAUDE.md file.

First, let's understand what the PR is about:

  • Title: "fix: preserve validation feedback targets"
  • Summary: Fixes validation feedback round-trips for targets that are not simple attributes.
  • Changes:
    • Accept object-only validation feedback targets such as $Object in MDL.
    • Preserve qualified association targets such as $Object/Module.Object_Association as a single path segment.
    • Emit AssociationName when a validation feedback action does not have an AttributeName.
  • Validation: Various tests were run including specific tests for validation feedback.

Now I'll go through the checklist systematically:

Overlap & duplication

  • Check proposals list above for existing proposals covering the same functionality: There's no proposals directory mentioned, so I can't check this.
  • Check if the diff introduces code that duplicates existing implementations: I don't see obvious duplication. The changes are focused on validation feedback handling in microflows.
  • Check doctype test files above for existing test coverage of the feature area: The PR adds a new bug test file mdl-examples/bug-tests/358-validation-feedback-targets.mdl which is appropriate for a bug fix.
  • Flag if the PR re-documents already-shipped features as new: This is a bug fix, not documentation of existing features.

MDL syntax design (for new or modified MDL syntax)

The PR modifies the MDL grammar for validation feedback statements:

  • In mdl/grammar/MDLParser.g4:
    • Changed attributePath rule to: VARIABLE ((SLASH | DOT) qualifiedName)+ (was VARIABLE ((SLASH | DOT) (IDENTIFIER | qualifiedName))+)
    • Changed validationFeedbackStatement to accept (attributePath | VARIABLE) instead of just attributePath

Let me check if this follows the design guidelines:

  • Follows standard CREATE/ALTER/DROP/SHOW/DESCRIBE pattern: Validation feedback is not a CRUD operation, so this doesn't apply directly. It's a microflow statement.
  • Uses Module.Element qualified names everywhere: The grammar change ensures that $Object/Module.Association is treated as a single qualified name segment, which is correct.
  • Property lists use ( Key: value, ... ) format: Not applicable here as this is about validation feedback syntax.
  • Colon for property definitions (Key: value), AS for name-to-name mappings: Not applicable here.
  • Keywords are full English words: Uses "VALIDATION FEEDBACK" which is correct.
  • Statement reads as English: "validation feedback $Object/Email message '...'" reads as English.
  • No keyword overloading: "VALIDATION FEEDBACK" has one consistent meaning.
  • Diff-friendly: Adding one property would be a one-line diff - not directly applicable here.
  • Consistent with existing MDL patterns: Looking at the existing doctest files, validation feedback statements follow the pattern validation feedback $Object/Attribute message '...'.

The grammar changes seem reasonable:

  1. Allowing VARIABLE as a target (for object-only validation feedback like validation feedback $Object message '...')
  2. Making attributePath treat $Object/Module.Association as a single segment (so the dot in "Module.Association" doesn't split it into multiple segments)

This addresses the issue of preserving qualified association targets as a single path segment.

Full-stack consistency (for MDL features)

The PR modifies:

  • Grammar rule in MDLParser.g4
  • AST: I don't see explicit AST changes, but the grammar change would affect how the AST is built. Let me check...
    • Looking at the diff, I don't see changes to mdl/ast/ directory. This might be okay if the existing AST nodes can handle the new grammar variations.
  • Visitor: No explicit changes shown in the diff to mdl/visitor/ but the grammar change would affect how the visitor processes the parse tree.
  • Executor: Changes in:
    • mdl/executor/cmd_microflows_builder_calls.go (how validation feedback actions are built)
    • mdl/executor/cmd_microflows_format_action.go (how validation feedback actions are formatted back to MDL)
    • Added test files for the builder and formatter
  • LSP: No changes shown to cmd/mxcli/lsp.go - but validation feedback might not need LSP changes if it's just about parsing and formatting existing functionality.
  • DESCRIBE roundtrip: The PR includes tests for this via the roundtrip doctype tests and specifically mentions that describe output must keep the validation feedback statement.
  • VS Code extension: No changes shown to vscode-mdl/package.json - likely not needed for this fix.

Let me check if there are missing pieces:

  • The PR doesn't show changes to the AST or visitor explicitly, but the grammar change would require the visitor to handle the new alternatives in the validationFeedbackStatement rule.
  • Looking at the executor changes, they do handle both cases (attributePath and VARIABLE), so the visitor must be producing the correct AST.

Actually, looking more carefully at the grammar change:

validationFeedbackStatement
-    : VALIDATION FEEDBACK attributePath MESSAGE expression (OBJECTS LBRACKET expressionList RBRACKET)?
+    : VALIDATION FEEDBACK (attributePath | VARIABLE) MESSAGE expression (OBJECTS LBRACKET expressionList RBRACKET)?

This means the visitor would need to handle both attributePath and VARIABLE as children of the validationFeedbackStatement rule. Since the PR doesn't show visitor changes, I should check if this is handled implicitly.

In ANTLR, when you have a rule like A : (B | C);, the visitor would get a context where you'd need to check which alternative matched. The executor changes show they're handling both cases, so presumably the visitor is correctly setting up the AST.

Let me look for AST definitions related to validation feedback. I don't see explicit AST changes in the diff, but the existing AST likely has a way to represent both attribute paths and bare variables.

Looking at the executor changes in cmd_microflows_builder_calls.go, I see:

fb.addValidationFeedbackAction(&ast.ValidationFeedbackStmt{
	AttributePath: &ast.AttributePathExpr{
		Variable: "FormObject",
		Segments: []ast.PathSegment{
			{Name: "Synthetic.FormObject_Target", Separator: "/"},
		},
	},
	// ...
})

And in the formatter:

} else if a.AssociationName != "" {
	attrPath = varName + "/" + a.AssociationName
}

This suggests the AST ValidationFeedbackStmt has an AttributePath field that can be nil (for object-only targets) or populated.

Let me check if there are AST changes not shown in the truncated diff... Actually, the diff shows changes to many files, but I don't see mdl/ast/ in the file list of changes shown. Given the truncation note ("NOTE: The diff was truncated to 80k characters. Total size: 957916 bytes."), it's possible AST changes exist but weren't shown.

However, looking at what's shown:

  • Grammar changes ✓
  • Executor changes (builder and formatter) ✓
  • Tests ✓
  • Roundtrip test updates ✓

The visitor isn't explicitly shown, but it's likely that the existing visitor can handle the grammar change because:

  1. The attributePath rule was modified but still returns the same type of structure
  2. The new alternative (VARIABLE) would likely produce an AttributePathExpr with zero segments or some special marker

Actually, looking at the executor test:

fb.addValidationFeedbackAction(&ast.ValidationFeedbackStmt{
	AttributePath: &ast.AttributePathExpr{
		Variable: "FormObject",
		Segments: []ast.PathSegment{
			{Name: "Synthetic.FormObject_Target", Separator: "/"},
		},
	},
	// ...
})

And for object-only:

action := &microflows.ValidationFeedbackAction{
	ObjectVariable: "Customer",
	// ...
}

This suggests that when AttributePath.Segments is empty, it's treated as an object-only target.

The grammar change to attributePath:

attributePath
-    : VARIABLE ((SLASH | DOT) (IDENTIFIER | qualifiedName))+
+    : VARIABLE ((SLASH | DOT) qualifiedName)+

This ensures that $Object/Module.Association is parsed as:

  • Variable: "Object"
  • Segment: {Name: "Module.Association", Separator: "/"}

Whereas before it might have been split as:

  • Variable: "Object"
  • Segment 1: {Name: "Module", Separator: "/"}
  • Segment 2: {Name: "Association", Separator: "."} [if IDENTIFIER was allowed]

But with the change to require qualifiedName after the slash/dot, and given that qualifiedName likely includes dots, this keeps Module.Association as a single unit.

Actually, looking more closely at the grammar change comment:

// Shared by SET, LOOP, aggregate expressions, and validation feedback targets.
// The segment is a qualifiedName so `$Obj/Module.Association` remains a
// single slash segment rather than being split at the dot.
attributePath
-    : VARIABLE ((SLASH | DOT) (IDENTIFIER | qualifiedName))+
+    : VARIABLE ((SLASH | DOT) qualifiedName)+

This confirms the intent is to keep Module.Association as a single qualifiedName token.

Now, for the object-only case (validation feedback $Object message '...'), the grammar change allows:

validationFeedbackStatement
    : VALIDATION FEEDBACK (attributePath | VARIABLE) MESSAGE expression ...

So $Object would match the VARIABLE alternative directly.

In the executor, when processing this, they check:

if len(s.AttributePath.Segments) == 0 {
    // Handle object-only case
}

This makes sense.

Test coverage

  • New packages should have test files: Not applicable (no new packages)
  • New executor commands should have MDL examples in mdl-examples/doctype-tests/: The PR adds a bug test, not a doctype test, but bug tests are appropriate for bug fixes.
    • It adds mdl-examples/bug-tests/358-validation-feedback-targets.mdl
  • Integration paths should be tested, not just helpers: The PR includes:
    • go test ./mdl/executor -run TestFormatAction_ValidationFeedback
    • go test ./mdl/visitor -run TestValidationFeedback
    • And adds specific unit tests for the builder and formatter
  • No time.Sleep for synchronization: I don't see any time.Sleep in the test changes shown.

Looking at the watcher test change:

// Rapidly write 5 times — should debounce into a single message.
+	// Keep the burst tighter than the debounce window so slow CI machines do
+	// not accidentally let an intermediate timer fire.
	for i := range 5 {
		_ = os.WriteFile(unitFile, []byte{byte('a' + i)}, 0644)
-		time.Sleep(50 * time.Millisecond)
	}

They REMOVED a time.Sleep, which is good! This addresses the checklist item about not relying on time.Sleep for synchronization.

Security & robustness

  • Command injection, XSS, SQL injection, temp file issues: I don't see any obvious issues.
  • Unix sockets should use restrictive permissions (0600): Not applicable here.
  • File I/O should not be in hot paths: The watcher changes are about file system watching, but the changes improve the debouncing logic, which is good.
  • No silent side effects on typos: The grammar change is more restrictive (requiring qualifiedName after slash/dot), which should reduce false positives.
  • Correct method receivers: I don't see any obvious issues with method receivers in the changes shown.

Scope & atomicity

  • Each commit should do one thing: The PR has multiple commits but they're all related to the same fix.
  • PR should be scoped to a single feature or concern: Yes, it's focused on validation feedback targets.
  • Independent features should be in separate PRs: Not applicable.
  • Refactors touching many files should be their own commit: The changes are focused and related.

Code quality

  • Refactors should be applied consistently: The changes are consistent within the scope.
  • Manually maintained lists should be flagged as maintenance risks: I don't see any such lists.
  • Design docs should match the actual implementation: The implementation follows the described fix.

Bugs & correctness

  • Logic errors, race conditions, resource leaks:
    • The watcher debouncing fix addresses a potential race condition where rapid file changes could trigger multiple events.
    • The validation feedback fixes address logic errors in parsing and formatting.
  • Error handling gaps: I don't see obvious error handling gaps.

Let me double-check the MDL syntax design aspects since that's crucial:

The PR modifies the validation feedback statement to accept:

  1. attributePath (existing behavior for things like $Object/Attribute)
  2. VARIABLE (new for object-only like $Object)

This follows the design guidelines:

  • Uses standard CRUD verbs? Not applicable (it's a microflow statement)
  • Reads as English: "validation feedback $Object message '...'" - yes
  • Qualified names: The grammar ensures $Object/Module.Association stays as one segment - yes
  • Property format: Not applicable here
  • LLM-friendly: One example should suffice - seems reasonable
  • Diff-friendly: Not directly applicable, but the changes are focused
  • Consistent with existing patterns: Looking at existing doctest files, validation feedback statements exist and the changes extend them logically

Let me check if there are any issues with the AS keyword usage:
The PR doesn't introduce any new AS usage in incorrect contexts. The AS keyword is still used for aliasing in other contexts (like in SELECT statements), which is correct.

One potential concern: In the validation feedback statement, we now have:

validation feedback $Object message '...'

Is there any risk of confusion with AS? For example, could someone mistakenly write:

validation feedback $Object AS SomeAlias message '...'

But looking at the grammar, it's:

validationFeedbackStatement
    : VALIDATION FEEDBACK (attributePath | VARIABLE) MESSAGE expression ...

So after the VARIABLE/attributePath, it expects MESSAGE, not AS. So AS wouldn't be valid in this position, which is correct.

Actually, let me check if AS is allowed anywhere in validation feedback. Looking at the full rule from the diff:

validationFeedbackStatement
    : VALIDATION FEEDBACK (attributePath | VARIABLE) MESSAGE expression (OBJECTS LBRACKET expressionList RBRACKET)?

So the structure is:
VALIDATION FEEDBACK MESSAGE [OBJECTS []]

AS is not part of this statement, which is correct.

Now, let me verify the specific changes against the issue description:

Issue: "Fixes validation feedback round-trips for targets that are not simple attributes."

Changes:

  1. "Accept object-only validation feedback targets such as $Object in MDL."

    • Grammar change: validationFeedbackStatement now accepts VARIABLE
    • Executor/formatter changes: Handle the case where AttributeName is empty (object-only)
    • Tests: Added TestFormatAction_ValidationFeedback_ObjectOnlyTarget
  2. "Preserve qualified association targets such as $Object/Module.Object_Association as a single path segment."

    • Grammar change: attributePath now requires qualifiedName after SLASH/DOT, so Module.Object_Association stays as one token
    • Executor changes: In cmd_microflows_builder_calls.go, the logic now checks strings.Count(segs[0].Name, ".") to determine if it's an association (1 dot) vs attribute (0 dots) vs fully qualified attribute (2+ dots)
    • Tests: Added TestAddValidationFeedbackAction_PreservesQualifiedAssociationTarget
  3. "Emit AssociationName when a validation feedback action does not have an AttributeName."

    • Formatter changes: In cmd_microflows_format_action.go, added:
      } else if a.AssociationName != "" {
          attrPath = varName + "/" + a.AssociationName
      }
    • Tests: Added TestFormatAction_ValidationFeedback_AssociationTarget

All three points are addressed.

Let me check if the changes are minimal and focused:

  • Grammar: 2 small changes
  • Executor: Changes to builder, formatter, and added tests
  • Tests: Added specific unit tests and updated roundtrip expectations
  • Documentation: Added a bug test MDL file

This seems appropriately scoped.

One thing I should check: Does the PR maintain backward compatibility?

  • Existing validation feedback statements like validation feedback $Object/Attr message '...' should still work
    • Yes, because attributePath is still accepted as the first alternative
  • The grammar change to attributePath is more restrictive (requiring qualifiedName instead of (IDENTIFIER | qualifiedName)), but this should only affect invalid cases that were previously misparsed
    • For example, $Object/Attr where Attr is an identifier would still work because Attr is a qualifiedName (a simple qualified name with no dots)
    • Actually, qualifiedName in ANTLR grammar typically allows dots, so a simple identifier IS a qualifiedName
    • So this change shouldn't break existing valid cases

Let me verify what qualifiedName means in this grammar. Since I don't have the full grammar, I'll assume that qualifiedName is defined to include dotted names, so a simple identifier is still a qualifiedName (just one with zero dots).

The comment explains it: "// The segment is a qualifiedName so $Obj/Module.Association remains a single slash segment rather than being split at the dot."

This suggests that qualifiedName can contain dots, so $Object/Attribute would parse as:

  • Variable: $Object
  • Segment: {Name: "Attribute", Separator: "/"}

Whereas before the change, if the grammar was (IDENTIFIER | qualifiedName), it might have tried to match IDENTIFIER first for "Attribute", but since qualifiedName also matches, and they're ordered, it might have depended


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

@ako ako merged commit e2d44d1 into mendixlabs:main May 2, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Preserve validation feedback object and association targets

3 participants