diff --git a/.claude/skills/fix-issue/findings/mdl-executor.jsonl b/.claude/skills/fix-issue/findings/mdl-executor.jsonl index a793c7a09d..5cdf70b25d 100644 --- a/.claude/skills/fix-issue/findings/mdl-executor.jsonl +++ b/.claude/skills/fix-issue/findings/mdl-executor.jsonl @@ -876,6 +876,11 @@ {"area": "mdl/executor", "date": "2026-10-07", "symptom": "A button added to a snippet with `alter snippet … insert` (or replace) calling `call microflow M.F(P = $P)` with a snippet parameter fails mxbuild with CE0115 \"The arguments that are passed to microflow 'M.F' do not match the expected parameters and need to be refreshed\" on that button only; the same button in CREATE SNIPPET or via ALTER SNIPPET SET is clean", "cause": "`buildWidgetsFromAST` and `buildColumnSpecsFromAST` (mdl/executor/cmd_alter_page.go) seeded paramScope and localVariables from the mutator but not `isSnippet`, so `classifyFlowArgValue` returned kind \"parameter\" and the Forms$PageVariable named PageParameter instead of SnippetParameter", "file": "`mdl/executor/cmd_alter_page.go` (isSnippet from mutator.ContainerType() in both builders)", "insight": "**A pageBuilder built outside CREATE needs THREE scope fields, not two** — paramScope, localVariables and isSnippet; #1317 found the SET builder missing all three and this one missing the last. When adding a builder, copy the seeding from convertASTAction rather than from memory. **Unlike the Expression-form defect (#1140/#1317), mxbuild DOES catch the wrong slot** — CE0115 rather than CE1571 — so one script exercising CREATE, INSERT and SET on the same snippet with `mx check` is a complete A/B: only the broken path's button is named. Verified on 11.12.2: 1 error before, 0 after", "refs": ["mendixlabs/mxcli#1317", "mendixlabs/mxcli#1140"], "ce": ["CE0115"]} {"area": "mdl/executor", "date": "2026-10-07", "symptom": "`create or modify microflow M.F () begin @excluded declare $Variable Boolean = false; return; end;` reports success but the activity is written enabled (ActionActivity Disabled=false); DESCRIBE prints no @excluded, so the round trip of any microflow with a disabled activity re-enables it. Same for every action statement (log, call, change, ...)", "cause": "`flowBuilder.mergeStatementAnnotations` copies ast.ActivityAnnotations into fb.pendingAnnotations field by field and never copied `Excluded`. The visitor set it, applyAnnotations honoured it (activity.Disabled = true), the backend writes Disabled, the describer emits @excluded — every layer was right except the hand-written merge between them", "file": "`mdl/executor/cmd_microflows_builder_annotations.go` (mergeStatementAnnotations)", "insight": "**Bisect by layer with the cheapest probe at each**: a visitor probe showed the AST carried Annotations, `bson dump` showed Disabled=false on disk, so the loss was between AST and write — and grepping `Disabled` showed exactly one setter, which was fine, pointing at its input. A field-by-field copy of a struct is a silent-drop site for every field added after it was written; the guard is a reflection test that sets every field and asserts it survives, with an explicit allowlist (Start, Invalid*, UnknownNames) for fields consumed elsewhere. Do not chase the backend or describer — both were already correct", "refs": ["mendixlabs/mxcli#1328"]} {"area": "mdl/executor", "date": "2026-10-07", "symptom": "`set $N = 1;` on a microflow/nanoflow/rule PARAMETER ($N: Integer or String) passes `mxcli check` and `exec`; mx check reports [CE7247] \"Parameter 'N' cannot be changed.\" at the Change variable activity", "cause": "Nothing modelled that a Change variable activity cannot target a parameter; the variable-kind tracking treated a primitive parameter like any declared primitive. Found while measuring MDL-SET01's object producers (#1323): the object parameter's CE7247 came back with a different wording, which was the tell that the restriction is on parameters, not on types", "file": "`mdl/executor/validate_microflow_set_object.go` (`checkSetOnObjectVariable` primitive-parameter branch, `setTargetViolations`); rules via `validate_program.go` (check) and `rule_validation.go` `validateRuleSetTargets` (exec, `cmd_rules_create.go`); MDL-SET01 added to `execEnforcedMicroflowRules` in `validate.go`", "insight": "When one measurement returns a different MESSAGE under the same CE code, test the cause the message names — here 'Parameter … cannot be changed' fired for an Integer too, a whole adjacent class. Measure the neighbours before writing the rule: list parameters (`set` = Change list Replace, `add`) and member changes on parameters BUILD, so the rule is 'any non-list parameter', not 'any parameter'. Rules never run ValidateMicroflow: plain check needs a ValidateProgram hook and exec a gate in cmd_rules_create; putting it in validateRule instead would double-print under --references, which runs both", "refs": ["mendixlabs/mxcli#1323"], "ce": ["CE7247"], "rules": ["MDL-SET01"]} +{"area": "mdl/executor", "date": "2026-10-07", "symptom": "`validation feedback $Input/Input_Person` — an ASSOCIATION written by its bare name — passes `check --references` and `exec`, then mx check fails **CE1613** \"The selected attribute 'G45.Input.Input_Person' no longer exists.\" at Validation feedback activity 'Show validation message on member Input_Person of Input'. The qualified `$Input/G45.Input_Person` builds clean", "cause": "`addValidationFeedbackAction` split attribute from association by DOT COUNT alone: 0 dots = attribute, so a bare association name was always qualified as `Entity.Name` into AttributeName. The change-object writer (`resolveMemberChange`) already looked the bare name up in the domain model; validation feedback never got that lookup", "file": "`mdl/executor/cmd_microflows_builder_calls.go` (`bareFeedbackMember`), `mdl/executor/entity_hierarchy.go` (`DeclaringAssociationRef`)", "insight": "Same slot as #974 (inherited attribute → CE1613), one branch over: #974 fixed WHICH entity qualifies a bare attribute, and left the assumption that a bare name IS an attribute. When a writer chooses attribute vs association from the spelling's shape, ask what a dotless name can be — in MDL it is either. Resolution order: attribute in the generalization chain first (unchanged behaviour), then an association the entity or an ancestor OWNS (ParentID — searched in that link's own module, Associations AND CrossAssociations), else the old spelling so mxbuild still names it. Fixed output is byte-for-byte the qualified form's, so 'the qualified form builds' (the reporter's control) carries over; reproduced and cleared on 11.14.0 with two copies (pre-fix binary: the reported CE1613; fixed: 0 errors). Other bare-member writers that still decide by dot count are worth grepping for `strings.Count(` on a member name", "refs": ["mendixlabs/mxcli#1322", "mendixlabs/mxcli#974"], "ce": ["CE1613"], "rules": []} +{"area": "mdl/executor", "date": "2026-10-07", "symptom": "`retrieve … where Kind = $Filter` (Enumeration attribute, String variable), `Email >= $Since` (String vs DateTime), `Visits = $Text` (Integer vs String) pass `check --references` and `exec`; mx check reports [CE0161] \"Error(s) in XPath constraint.\" at Retrieve object(s) activity (mendixlabs/mxcli#1325)", "cause": "The --references pass resolved a constraint's members against the entity (validateRetrieveMembers, #1213) but never compared the member's type with the right-hand variable's declared type", "file": "`mdl/executor/validate_retrieve_operand_types.go` (`validateRetrieveOperandTypes`, `xpathOperandTypesCompatible`), wired beside `validateFlowBodyReferences` for microflows and nanoflows in `validate.go`", "insight": "Measure the whole matrix before writing the rule — one mx check over 8 attribute types x 8 variable types x {=, >=} with a unique `@caption` per retrieve (CE0161 names only the activity caption, not the microflow). The result is NOT \"types must be equal\": a String variable against a DateTime attribute builds clean, Integer/Long/Decimal compare freely, an enumeration needs the SAME enumeration. Separately measured but out of scope: `>=` on a Boolean or Enumeration attribute is CE0161 even against a same-typed variable. The flow collector has no parameter list, so the check takes `s.Parameters` from the create statement; variable types = params + `declare`, anything else (objects, lists, loop iterators) stays silent.", "refs": ["mendixlabs/mxcli#1325", "mendixlabs/mxcli#1213", "mendixlabs/mxcli#176"], "ce": ["CE0161"], "rules": []} {"area":"mdl/executor","date":"2026-10-07","symptom":"`mxcli check script.mdl -p app.mpr --references` reported \"Check passed!\" for a declared type naming an entity that does not exist: a microflow/nanoflow/rule parameter or return type (`$p: System.Nope`, `$p: M.Nope`, `list of System.Nope`, `returns System.Nope`) and a page/snippet parameter (`params: ($x: System.Nope)`). Originally reported as `create association M.X_Y from System.Nope to M.Y` passing — that shape was already refused on main (#555).","cause":"validateWithContext resolved a flow's BODY (retrieve/create entity refs), an association's endpoints and an EXTENDS target, but never a document's SIGNATURE. exec refuses the user-module shapes and every page/snippet parameter after earlier statements are written; for a System entity in a flow signature it skips resolution (isBuiltinModuleEntity) and writes the dangling name.","file":"mdl/executor/validate_declared_types.go (flowSignatureErrors, documentParameterErrors); wired in validate.go for microflow/nanoflow/rule/page/snippet","insight":"Reproduce the reported shape on current main before fixing it: it had been fixed four days after the report, and the real hole was one statement-part over. Map a reference check by WHERE a name sits (body / endpoint / generalization / signature), not by module: System was never skipped wholesale — the virtual System domain model makes System.User resolve and System.Nope not, through the same buildEntityQualifiedNames as any module. A bare `Module.Name` type is ambiguous (entity or enumeration — `$d: System.DeviceType` is an enum), so accept either; only the explicit Enumeration(...) spelling is enum-only. A mock backend without the System DM must not make every System type 'missing' — absence of the whole module is not evidence about one name. Measure on the real path: the PedApp fixture through ValidateProgram + ValidateProgramWithWarnings (agreeCheck), not a MockBackend. Still open: `grant … on entity System.Nope to M.Role` and a grant to a non-existent module role pass --references; a java action parameter typed System.Nope also passes check (exec's verdict on it not measured).","refs":["#555","#610","#972"]} {"area": "mdl-executor", "date": "2026-10-07", "refs": ["mendixlabs/mxcli#1324"], "symptom": "A widget directly inside data view `dvP` that reads `$dvP` OUTSIDE an action — a nested data view's `DataSource: microflow M.F(Gate = $dvP)`, `Visible: $dvP/Name != ''`, `Editable: …`, `DynamicClasses: …`, or a nested list's `database from M.E where [Name = $dvP/Name]` — passes `check` and `exec`, then `mx check` reports `[CE0117] \"Error(s) in expression.\"` at the widget (CE0161 \"Error(s) in XPath constraint.\" for the `where`)", "cause": "MDL-BUTTON02 (the #1324 fix) only looked at action arguments, though every slot evaluated in the widget's enclosing context has the same scope: a container's widget-name variable exists only one data container below it", "file": "`mdl/executor/validate_page_button_context.go` (`checkOwnContainerName` now takes the widget and walks action args, `GetDataSource().Args` / `.Where`, and `ownNameExprProps`)", "insight": "**A scope rule belongs to the context, not to the slot the report happened to use.** The follow-up question that settled it was one probe per slot with a control one data view deeper: five of five slots failed in the own context and all five controls built clean, so the rule is 'anything evaluated in this widget's enclosing context' — enumerate those slots rather than wait for one report each. Two things that would have wasted a build: (1) `Visible:`/`Editable:` arrive in the AST as `VisibleIf`/`EditableIf` strings (dump `w.Properties` before keying on what the author wrote), and (2) text-template parameters (`ContentParams ({1} = $dvP/Name)`) are already refused by MDL-WIDGET24 for an unrelated reason (a template parameter is an attribute name, not a variable path), so they are not a scope case at all. The XPath slot reports a different code (CE0161), which is why the message carries the code per slot. Control: stubbing the data-source and property slots fails the new test with `flagged \"\"`; the `$currentObject` rewrite of all five builds at 0 errors. Repro `mdl-examples/bug-tests/1324-own-data-container-name-outside-actions.fail.mdl`"} {"area": "mdl/executor", "date": "2026-10-07", "symptom": "`describe structure depth 2|3` / `mxcli structure -d 3` never annotates a page with its data widgets (`Page M.P [DataView, …]`), on any project and with a full catalog — every page prints bare. Evora Factory Management: 0 of 54 pages annotated, although CATALOG.widgets holds their data views and grids.", "cause": "structurePages queried `widgets … where ParentWidget = ''`; widgets_data has never had a ParentWidget column (the tree position, added later, is ParentWidgetId + Depth). The query failed and the error was discarded (`if err == nil { … }`), so the annotation was dead code from the initial commit. Every other catalog query in cmd_structure.go swallowed errors the same way (`if err != nil || len(rows) == 0 { return }`).", "file": "`mdl/executor/cmd_structure.go` (`structureQuery`, `structurePages`, `queryCountByModule`, `shortWidgetType`), `mdl/executor/cmd_structure_page_widgets_test.go`", "insight": "A fixed SQL query against a table the builder owns can only fail through drift inside mxcli, never because of the user's project, so swallowing its error converts a schema mismatch into silent absence — the same shape as the depth-1 flow-count casing bug (#717), which a swallowed error also hid. Route such queries through one helper that returns the error; then a column rename fails the first test that runs the command against a real built catalog. That test must build the catalog with catalog.NewBuilder in FULL mode (SetFullMode(true)) over raw page BSON from GetRawUnitFunc — widgets_data is empty in fast mode, and a fast-mode test passes against the broken query because 'no widgets' and 'query failed' both print a bare page. Mock gotcha: the builder dereferences GetNavigation, whose mock default is (nil, nil), so stub it with an empty NavigationDocument. Semantics choice: 'top-level' cannot mean Depth = 0 — real pages wrap content in a layout grid, so a root filter lists almost nothing; list data widgets with no data-widget ancestor instead (walk ParentWidgetId). Measured on Evora after the fix: 45 of 54 pages annotated (181 of 212 with `all`), and the 9 bare pages have no data widget in CATALOG.widgets.", "refs": ["#717"]} +{"area": "mdl/executor", "date": "2026-10-07", "symptom": "A Microflow-typed Java action argument with `)` on the next line — `call java action M.DoSomething(Flow = M.SUB_Target⏎ );` — reported on 0.23/0.24 as passing check/exec and then failing mx check with CE1613 \"The selected microflow 'MyModule.SUB_Target⏎' no longer exists\"; on current main the same script is REFUSED by both `check --references` and exec as \"parameter Flow is of type Microflow, which takes a microflow name ('Module.Microflow'), not MyModule.SUB_Target\" — the message names a valid microflow name. `Flow = empty⏎)` was refused too (or, without the guard, stored the text `empty` as a microflow reference).", "cause": "buildCallArgumentList keeps the whitespace between an argument's last token and `)` by wrapping the value in ast.SourceExpr (expression round-trip). The Microflow-typed path type-switched on the wrapper: microflowParamArgRefusal (#1210) and isEmptyJavaActionArgument saw a SourceExpr, not a QualifiedNameExpr / LiteralExpr. The original stored-newline form had already been removed by #898 (mendixexpr.String trims a SourceExpr's trailing whitespace), which is why the reported symptom no longer reproduces and the bug now surfaces as a false refusal instead.", "file": "`mdl/executor/javaaction_flow_param.go` (`javaActionArgumentValue`, `microflowParamArgRefusal`), `mdl/executor/cmd_microflows_builder_calls.go` (`isEmptyJavaActionArgument`, Microflow-typed mapping), `mdl/executor/javaaction_flow_param_whitespace_test.go`", "insight": "Any executor decision that type-switches on a call/member argument (`arg.Value.(type)`) must unwrap ast.SourceExpr first: the visitor wraps the value only when whitespace follows it, so the hand-built QualifiedNameExpr/LiteralExpr in existing unit tests never exercises the wrapped form and every such switch passes its tests. Build test arguments with visitor.Build from real MDL, with and without a line break before `)`. Wrong turn to skip: the issue blames the lexer/name text — the name is parsed cleanly; the trailing whitespace is a deliberate SourceExpr suffix, and stripping it in the visitor would break describe round-trip layout (#898). Control: reverting the fix with #1210's guard stubbed no longer reproduces the newline on this tree (#898 trims it), so the live regression is the refusal, which the reverted fix reproduces in both the builder test and the check/exec agreement test.", "refs": ["mendixlabs/mxcli#1282", "mendixlabs/mxcli#1210", "#898"], "ce": ["CE1613"]} +{"area": "mdl/executor", "date": "2026-10-07", "symptom": "`mxcli check script.mdl -p app.mpr --references` printed \"Check passed!\" for `grant read * on entity System.Nope to M.R`, for a grant to `M.NopeRole`, and for every other GRANT/REVOKE form naming a missing entity, document, member or role; also for `create user role R ( ModuleRoles: (M.NopeRole) )`, `alter user role … add module roles`, and a demo user with an unknown user role or entity (measured on Evora Factory Management, 10.24.15). exec refused the grants after the earlier statements were written; the user-role and demo-user shapes it wrote unresolved (CE1613 at build).", "cause": "No validate path resolved a security statement's names. validateWithContext's switch had no case for any of them (only the role-free validateCrossModuleGrant ran), and the user-role/demo-user executors store module and user role names verbatim without resolving them, so neither check nor exec stood between a typo and the model.", "file": "`mdl/executor/validate_grant_refs.go` (`validateGrantReferences`), wired into `validateProgramWithWarnings` in `validate.go`; test `check_grant_references_pedapp_test.go`", "insight": "Two things made the obvious version wrong. (1) What check refuses has to be what exec refuses, form by form: exec refuses an unknown role on a GRANT and on an entity REVOKE, but a document REVOKE (and `alter user role … drop module roles`) of an unknown role is a reported no-op, which keeps cleanup scripts re-runnable after the role is dropped — refusing it would make check louder than exec. Likewise every System entity is refused by exec (refuseSystemEntityGrant), so `grant … on entity System.User` must be refused by check too, while System MODULE roles in a user role must pass. (2) A script's own effects are not only its CREATE statements: creating a microflow/nanoflow/page in a module with no module roles auto-creates `.User` (defaultDocumentAccessRoles), and doctype-tests/02b grants to exactly that role — the first version flagged 10 statements there. Walking the program in statement order (not the whole-program scriptContext) gives the forward-reference hint for free. Performance trap: reading every module's security costs ~5s on Evora; read only the modules a statement names (~0.1s).", "refs": ["mdl-examples/bug-tests/check-grant-unknown-reference-refused.mdl", "mdl-examples/bug-tests/check-grant-unknown-reference.mdl", "ako/mxcli#1020"]} +{"area": "mdl/executor", "date": "2026-10-07", "symptom": "A DataGrid 2 column's `Visible:` expression is stored as `true` (always visible) for every spelling but the old quoted one: `Visible: $showPrices`, `Visible: if … then … else …`, `visible: not(…)`, `Visible: [cond]`. check clean, exec reports \"Created page\", describe shows no Visible. `alter page … set (Visible: ) on grid column(…)` is refused as \"column property VisibleIf not found\"; `insert` of such a column drops it, and drops `Visible: false` too.", "cause": "The visitor lowers every expression spelling of Visible to the key VisibleIf (the page-widget conditional-visibility key) and keeps only plain values under Visible. The column writers read only visible/Visible: the widget engine by schema key + types.ItemPropertyAliases (no VisibleIf alias), the ALTER mutator's resolveColumnPropertyKey the same table, and widgetobj.BuildDataGrid2Column (ALTER insert/replace via buildColumnSpecFromAST) the exact key \"Visible\" as a string only, so a bool false fell to the default too. The mirror image of widget-visible-expression.mdl, where page widgets read VisibleIf and dropped Visible.", "file": "`mdl/types/widget_item_aliases.go` (`visible` ← `VisibleIf`), `mdl/executor/widget_engine.go` (WidgetDefGeneratorVersion 18), `mdl/executor/cmd_pages_builder_v3_widgets.go` (`columnSpecProperties`), `mdl/executor/cmd_pages_describe_output.go` (column Visible via widgetConditionMDL), `mdl/executor/validate_column_visible_scope.go` (MDL-WIDGET43)", "insight": "When the visitor lowers one MDL property to two AST keys by value shape, every consumer must read both — grep the consumers of the key the visitor writes, not of the property name. Making the value persist exposed what the drop had hidden: a column's visible expression has no row object (no dataSource in the widget schema, unlike columnClass), so every $currentObject example became CE0117 at build — measured on 11.14.0 — and needed a check rule (MDL-WIDGET43) in the same change. The widget def's per-item `dataSource` field is the signal for whether $currentObject exists.", "refs": ["mdl-examples/bug-tests/datagrid-column-visible-expression.mdl", "mdl-examples/bug-tests/widget-visible-expression.mdl"]} diff --git a/.claude/skills/fix-issue/findings/mdl-grammar.jsonl b/.claude/skills/fix-issue/findings/mdl-grammar.jsonl index e6ae03c975..e416bd9187 100644 --- a/.claude/skills/fix-issue/findings/mdl-grammar.jsonl +++ b/.claude/skills/fix-issue/findings/mdl-grammar.jsonl @@ -72,3 +72,4 @@ {"date": "2026-10-02", "area": "mdl/grammar", "symptom": "ako/mxcli#533: `call microflow M.MF_B(P: $P)` is a parse error (`mismatched input ':' expecting '='`) while `show page M.P(P: $P)` and a page action/data source `M.F(P: $P)` parse with an MDL-DEPR007 warning \u2014 the same argument has a different set of accepted spellings per call site", "cause": "MDL-DEPR007 (`Param: e`, R4) was registered as an alias, but only showPageArg and microflowArgV3 carried the `identifierOrKeyword COLON expression` alternative; callArgument (call microflow/nanoflow/java action/javascript action/external action/web service/database query) accepted only `=`", "file": "mdl/grammar/domains/MDLMicroflow.g4 callArgument; mdl/visitor/visitor_argument_binding.go ExitCallArgument", "fix": "Add `parameterName COLON /* @alias MDL-DEPR007 */ expression` to callArgument; ExitCallArgument records MDL-DEPR007 with the ` = ` rewrite (recordColonArgument now takes any name context). Kept the registry's RemovedIn 2: under ADR-0011 an alias deprecated in the mdl 1 era warns under mdl 0 and mdl 1 and is refused only from mdl 2 \u2014 refusing it under mdl 1 would change the frozen mdl 1 for show page", "insight": "When an alias is registered, grep every rule that binds the same construct (here: every argument rule) \u2014 a registry entry names a spelling, not the rules that accept it, so the alias coverage drifts per call site", "test": "mdl/visitor/visitor_argument_binding_test.go TestArgumentBindingAliases (call * colon cases); mdl/upgrade/argument_binding_test.go TestUpgrade_ColonArgumentOnCallStatements"} {"date": "2026-10-02", "area": "mdl/grammar", "symptom": "ako/mxcli#533, #569: a positional data-source argument `listview lv (datasource: microflow M.DS($Value))` reports `no viable alternative at input 'datasource'` at the property keyword, plus two cascade errors \u2014 not at the argument, and naming nothing; the create-page skill (widgets.md) and `syntax page.datasource` taught the positional form", "cause": "A widget property is predicted with full-LL lookahead over the whole `datasource: \u2026` alternative, so a failure anywhere inside it is reported at the property's first token. No argument rule had a positional alternative", "file": "mdl/grammar/domains/MDLPage.g4 microflowArgV3; mdl/grammar/domains/MDLMicroflow.g4 callArgument, showPageArg; mdl/visitor/visitor_argument_binding.go refusePositionalArgument", "fix": "Each argument rule gets a last `| expression` alternative that the visitor always refuses, at the argument's own line:column, naming `Param = ` and the syntax topic. Last so the ambiguity with `Param = e` (`=` is equality in an expression) resolves to the named alternative. call workflow's `(callArgumentList | VARIABLE)` reordered to `(VARIABLE | callArgumentList)` so `call workflow M.W($ctx)` keeps meaning the context variable. Docs corrected to `M.GetData(Param = $Param)`", "insight": "To move an ANTLR error to where the mistake is, accept the mistake in the grammar and refuse it in the visitor \u2014 the error-alternative pattern. A malformed named argument (`Param = )`) still reports at `datasource`: the general prediction-level case is not fixed by this", "test": "mdl/visitor/visitor_argument_binding_test.go TestPositionalArgumentIsRefusedAtTheArgument"} {"date": "2026-10-05", "area": "mdl/grammar", "symptom": "mendixlabs/mxcli#1288: `add $Row to $Material/MyFirstModule.SkuRowsOfMaterial;` -> `Parse error: line 9:23 mismatched input '/' expecting ';'`. The only spelling left was `change $Material (Assoc = $Row)`, which ASSIGNS the reference set: in a loop of three appends two rows were orphaned with 0 errors from check, mx check, runtime.log and a boolean-return test. Read side had the same defect: DESCRIBE of a Studio Pro Add/Remove member printed `change $Obj (Assoc = $X)`, so a describe -> exec round trip turned an append (and even a Remove) into a set assignment", "cause": "addToListStatement/removeFromListStatement only took `TO VARIABLE`, so the Change object activity's Add/Remove member types (MemberChangeTypeAdd/Remove, already in sdk/microflows and round-tripped by the codec) had no writer; formatAction ignored MemberChange.Type", "file": "mdl/grammar/domains/MDLMicroflow.g4 associationMemberTarget; mdl/visitor/visitor_microflow_actions.go; mdl/executor/cmd_microflows_builder_actions.go addAssociationMemberChange; mdl/executor/cmd_microflows_format_action.go associationMemberChangeStatement; cmd_microflows_builder_graph.go collect{List,Object}InputVariables; validate_microflow_retrieve_single.go listUseOf", "fix": "ADD expr TO $Obj/Module.Assoc and REMOVE $X FROM $Obj/Module.Assoc [commit] [refresh] -> AddToListStmt/RemoveFromListStmt with Association set -> ChangeObjectAction with one MemberChange{Type: Add|Remove}, built through addChangeObjectAction so the member resolves exactly like a CHANGE member. Refused on an attribute or a plain Reference. DESCRIBE prints a sole Add/Remove member back in that form; an Add/Remove mixed with other members gets a trailing `-- WARNING` instead of a silent `=`. The association form's $Obj is an object input, not a list input (else MDL-RETRIEVE01 and the retrieve-typing walkers misfire)", "insight": "Measured on 11.12.2 (mx check): Add on a ReferenceSet = 0 errors; Add on an attribute AND on a plain Reference = CE0033 \"The 'Type' property of this member cannot be 'Add'.\" The issue's claim that Reference was equally affected is about CHANGE set semantics, not about Add being valid there. When an AST statement gains a second meaning for an existing field (List = object variable here), grep every type-switch on the statement: the list-input walker and the single-object rule both read List as a list. And whenever a writer gains an enum value, check the describer prints it — the codec round-tripped Type all along; only formatAction dropped it, which is the silent half of this bug", "test": "mdl/visitor/visitor_add_to_association_test.go; mdl/executor/cmd_microflows_builder_add_to_association_test.go; cmd_microflows_format_action_test.go TestFormatAction_ChangeObject_AssociationAddRemove; mdl-examples/bug-tests/1288-add-remove-association-member.mdl", "refs": ["mendixlabs/mxcli#1288"], "ce": ["CE0033"]} +{"date": "2026-10-07", "area": "mdl/grammar", "symptom": "mendixlabs/mxcli#1281: two predicates on one XPath step fail to parse. `retrieve $x from M.Location where [M.A/M.TenantUser[Status = 'Active'][AnonymizedAt = empty]/M.B = '[%CurrentUser%]']` -> `mismatched input '/' expecting ';'`; ending the path on the step -> `missing ']' at '['`. One predicate with `and` passed, and Mendix accepts the consecutive form (reporter: access rules, mx check 0 errors on 11.12.4).", "cause": "xpathStep was `xpathStepValue (LBRACKET xpathExpr RBRACKET)?` and ast.XPathStep had a single Predicate field, so every consumer (serializers, the rename rewriter, the widget/retrieve member-ref walkers) assumed one.", "file": "mdl/grammar/domains/MDLPage.g4 xpathStep; mdl/ast/ast_expression.go XPathStep.Predicates; mdl/visitor/visitor_xpath.go buildXPathStep; mdl/visitor/visitor_page_v3.go xpathPathToString; mdl/executor/cmd_microflows_helpers.go; mdl/executor/validate_microflow.go; mdl/executor/validate_widget_member_refs.go; mdl/xpathrefs/rewrite.go", "fix": "`*` on the predicate group; Predicates []Expression in source order; every consumer loops. Kept separate rather than folded into one `and`: `[reversed()]` is also a step predicate and is not a condition, and folding would rewrite the user's constraint on describe.", "insight": "The stored-constraint reader (ParseXPathConstraint) did NOT report this as a failure: ANTLR recovery consumed the stray `[b]` up to EOF, so the EOF check from #772 passed and the round trip silently returned `Entity[a]` — dropping the second predicate. A grammar-level parse error with error listeners removed can still look like a full parse; test via visitor.Build (errors surfaced) as well as the listener-free helper. The ast field rename made the compiler enumerate every consumer — cheaper than grepping for walkers. mx check control: the scaffold's own `= '[%CurrentUser%]'` against a non-User entity gave CE0161 for BOTH the two-predicate and the `and` form; only the paired run showed it was the scaffold.", "test": "mdl/visitor/visitor_xpath_test.go TestXPath_ConsecutiveStepPredicates; mdl-examples/bug-tests/1281-consecutive-xpath-step-predicates.mdl"} diff --git a/.claude/skills/fix-issue/findings/mdl-other.jsonl b/.claude/skills/fix-issue/findings/mdl-other.jsonl index 1eee97cdd1..b889534f2b 100644 --- a/.claude/skills/fix-issue/findings/mdl-other.jsonl +++ b/.claude/skills/fix-issue/findings/mdl-other.jsonl @@ -99,6 +99,9 @@ {"area": "mdl/exprcheck", "date": "2026-10-04", "symptom": "ako/mxcli#969 item 1: `change $Log (Windrichting = if $Dir = 'NW' then E.NW else E.N)` on an enumeration attribute was refused with E001 (\"assigning an Enumeration attribute against a string literal\") for the 'NW' compared with a String; `find('|NW|NORTHWEST|', …)` inside the expression gave 3x E001 with fixes like `E.|`. exec runs check first, so a valid microflow could not be written", "cause": "parsePrimary ran checkStringLitVsSlot (E001) and the quoted-Boolean E002 on EVERY string literal while the slot path was set, so comparison operands, function arguments and if-conditions were judged as the slot's value", "file": "`mdl/exprcheck/parser.go` (checkValueLiterals)", "insight": "A slot constrains the VALUE, and the value is only the whole expression, a parenthesised one or a then/else result (recursively). Run slot-literal rules over the finished tree at those positions instead of during the parse, where the position is not yet known. Measured on 11.13.0: the comparison form builds clean, while a quoted 'NW' in a then-branch is CE0117 — so the branch rule is real and must keep firing. Note the WHOLE-literal form (`Wind = 'NW'`) builds clean because the writer rewrites it to the enum value; E001 there is stricter than mxbuild", "refs": ["#969"]} {"area": "mdl/exprcheck", "date": "2026-10-04", "symptom": "ako/mxcli#969 item 1 side finding: `change $Log (Wind = 'NW')` reported nothing in check when the enumeration and the entity were created in the same script; with them already stored it was E001", "cause": "TypeCheckProgram's CatalogReader is loaded from the catalog of the stored project only, so a script-declared attribute resolved to nothing, its kind was Unknown and every rule keyed on it stayed silent", "file": "`mdl/executor/typecheck.go` (declareScriptTypes), `mdl/exprcatalog/exprcatalog.go` (DeclareEnumeration, DeclareAttribute)", "insight": "Any catalog-backed check must overlay the script's own declarations, applied in statement order; Unknown is designed to suppress rules, so a missing overlay fails silent rather than loud. Test with the definitions in the SAME script as the use — the stored-project test passed all along", "refs": ["#969"]} {"area": "mdl/linter", "date": "2026-10-04", "symptom": "ako/mxcli#969 item 4: MPR006 warned that an empty container \"will crash at runtime\" (\"Did not expect an argument to be undefined\"), and the create-page skill told authors to pad every container with a dynamictext", "cause": "Claim from the initial commit with no measurement behind it", "file": "`mdl/linter/rules/empty_container.go`, `.claude/skills/mendix/create-page/SKILL.md`", "insight": "Measured on 11.13.0 (React client) with run --local --db-type hsqldb and a headless Chromium: a bare and a styled empty container render, the widgets after them are present, 0 console errors; mx check 0 errors. A runtime claim needs a runtime measurement — run --local + playwright is the layer (hsqldb avoids needing Postgres; the devcontainer needed the chromium shared libs apt-installed). Classic (Dojo) client not measured", "refs": ["#969"]} +{"area": "mdl/linter", "date": "2026-10-07", "symptom": "mendixlabs/mxcli#1319: an `import from mapping M.IMM($Json) first` activity already IN the model, over an OBJECT-rooted mapping, stores ForceSingleOccurrence=true + Range.SingleObject=true; `mxcli lint` said \"No issues found.\", `mx check` \"0 errors\", and the activity threw `key not found: Path(QName(None,),None,)` when it ran. check's MDL-MAP04 refuses the statement, but only in a script — models written by v0.24.0 or with `exec --no-check` were found by nothing", "cause": "MDL-MAP04 existed only as a check-time AST rule (validate_import_mapping_range.go). No lint rule read the stored ImportMappingCall flags against the mapping's root shape, and the shape helpers lived in the executor, which the lint rules package cannot import", "file": "`mdl/linter/rules/import_range_object_mapping.go` (new lint rule, same ID MDL-MAP04, registered in `cmd/mxcli/cmd_lint.go` builtinLintRules); `types.JsonMappingRootIsList` / `MappingRootPathCrossesArray` moved from `mdl/executor/cmd_microflows_builder_calls.go` to `mdl/types/mapping_shape.go`; example `mdl-examples/bug-tests/1319-lint-first-on-object-rooted-mapping.mdl`", "insight": "**A check-time rule guards only what passes through check; anything that can be written around it (older binaries, --no-check, Studio Pro, hand edits) needs a model-level twin.** When adding a check rule for a runtime-only failure, ask at once whether the same defect can already be IN models — if so ship the lint half with it. Key the model rule on the same shape decision as the builder/check (share the helper, don't re-derive it): the flags alone are NOT the defect, since a list-rooted mapping stores the identical FSO/Range pair for Studio Pro's legitimate First. Report either flag alone (#242 measured FSO=true/Range=false failing too). LintReader lacks mapping lookups: assert an optional interface on ctx.Reader() rather than widening LintReader, so test doubles stay valid. E2E without mxbuild: copy testdata/testapp-views (11.14.0), `exec --no-check` the broken script, `lint -r MDL-MAP04`; control = the pre-fix binary on the same model prints \"No issues found.\"", "refs": ["mendixlabs/mxcli#1319", "ako/mxcli#570", "ako/mxcli#242"], "rules": ["MDL-MAP04"]} {"area": "mdl/linter", "date": "2026-10-07", "symptom": "upstream mendixlabs/mxcli#1217: \"Lint CONV011 (NoCommitInLoop) misses CHANGE … COMMIT (and CREATE … COMMIT) inside a loop; only a separate COMMIT activity is flagged\". `change $T (\"Done\" = true) commit;` in a loop passed lint and `check`; `change …; commit $T;` was flagged. Studio Pro's recommender flags both (MXP004).", "cause": "CONV011 matched only `*microflows.CommitObjectsAction`; the Commit property of CreateObjectAction / ChangeObjectAction was never consulted. MDL-PERF01 (check-time twin, #1186) pinned its boundary to CONV011's, so it inherited the same gap by design.", "file": "`mdl/linter/rules/conv_loop_commit.go` (`committingActionKind`), `mdl/executor/validate_commit_in_loop.go` (ChangeObjectStmt/CreateObjectStmt cases); tests `conv_loop_commit_test.go`, `validate_commit_in_loop_test.go`; example `mdl-examples/bug-tests/1217-commit-clause-in-loop.mdl`", "insight": "**A rule that detects a database effect must enumerate every action that HAS the effect, not the action NAMED after it.** Commit is a property on create/change as well as an activity of its own; grep the action types for a `Commit` field before trusting a commit rule's coverage. Any value other than `No` commits — YesWithoutEvents skips handlers, not the round trip. When two rules share a pinned boundary (CONV011 ↔ MDL-PERF01), a coverage fix lands in both in the same change or they drift. Verified end to end: exec the repro into testdata/expr-checker/minimal.mpr and `lint -r CONV011` — reverted build flags 1 of 3 (only the separate commit), fixed build 3 of 3, list-commit-after-loop control quiet in both.", "refs": ["mendixlabs/mxcli#1217", "mendixlabs/mxcli#1186"], "rules": ["CONV011", "MDL-PERF01"]} {"area": "mdl/exprcheck", "date": "2026-10-07", "symptom": "mendixlabs/mxcli#1216: `declare $D DateTime = parseDateTimeUTC($Text, 'yyyy-MM-dd', empty);` — `mxcli check -p` reports `parseDateTimeUTC() expects 2 argument(s), got 3. [E006]` and exec refuses, while mx check on 11.14.0 reports 0 errors; only --no-check writes it", "cause": "funcTable listed the parse functions without their default-value overloads: parseDateTime/parseDateTimeUTC(value, format [, default]), parseInteger(value [, default]), parseDecimal(value [, format [, default]]) were all fixed at their minimum arity", "file": "`mdl/exprcheck/func_checker.go` (funcTable), test `mdl/exprcheck/parse_default_arity_test.go`, `mdl-examples/bug-tests/1216-parse-datetime-default-value.mdl`", "insight": "funcTable arities are a transcription, so widen each one only by measurement, and measure the siblings the reporter says 'presumably' — two of them (parseInteger/parseDecimal) had the same false E006, but parseBoolean($s, false) is CE0117 and stays 1-arg. Isolate each case in its own project copy: mx check names the activity by its caption ('Create Date and time variable'), so several cases in one project are indistinguishable. Plausible wrong turn: a default of currentDateTime() fails CE0117, which reads as 'the 3-arg UTC form is invalid' — it is currentDateTime() itself, rejected on its own in a microflow; vary the default ($var, empty) before concluding. E006 only fires with -p, so a project-less `mxcli check` of the repro passes and proves nothing.", "ce": ["CE0117"], "rules": ["E006"]} {"area": "mdl/exprcheck", "date": "2026-10-07", "symptom": "`declare $D DateTime = currentDateTime();` passes `mxcli check` and `exec`, then mx check on 11.14.0 fails `[error] [CE0117] \"Error(s) in expression.\" at Create variable activity 'Create Date and time variable'` — in a microflow and a nanoflow alike", "cause": "funcTable listed `currentDateTime` as a zero-argument built-in. Mendix has no such function; the current time is the `[%CurrentDateTime%]` token. funcTable is MDL044's sole allow-list, so the entry silenced the one rule that would have caught it", "file": "`mdl/exprcheck/func_checker.go` (entry removed), `mdl/exprcheck/unknown_funcs.go` (tokenFuncs → FuncRef.Token), `mdl/executor/validate_microflow.go` (MDL044 hint), bug tests `current-datetime-function.fail.mdl` / `current-datetime-token.mdl`", "insight": "Found by accident while measuring #1216: a default of currentDateTime() made parseDateTime look like it rejected a third argument. Before blaming the outer construct, build the inner expression on its own. The removal alone would give a useless hint — nearestFunc offers a spelling match, and the right answer is a token rather than a function — so name the token in the hint. Nothing in the repo emitted or recommended currentDateTime() (every example uses the token), which suggests the entry came from transcription, like the year()/month()/trunc() entries before it. The remaining unverified extraction names (dayOfYear, hour, …) are the same risk.", "ce": ["CE0117"], "rules": ["MDL044"]} +{"area": "mdl/linter", "date": "2026-10-07", "symptom": "A Starlark lint rule cannot tell which project languages are enabled: strings() returns rows for every stored translation (a fresh en_US-only 11.12.5 app already has 124 nl_NL rows), enabling nl_NL changes nothing a rule sees, and a rule calling languages() is skipped with 'undefined: languages (a builtin this mxcli does not have — the rule may need a newer mxcli)'", "cause": "No builtin exposed Settings$LanguageSettings.Languages; LintReader had no GetProjectSettings, so the only language data a rule could reach was the catalog's strings table, which indexes stored translations, not enabled languages", "fix": "languages() builtin returning struct language{code,is_default,check_completeness} from LintReader.GetProjectSettings (added to the interface; the backend already implements it). Classified CatalogFast; a reader error fails the rule instead of answering []", "insight": "Stored translations are not the enabled set: Studio Pro and the starter app keep texts in languages the project never enabled, so any 'per language' check built on strings() over-reports. Project-level facts belong on the reader path (like project_security()), not in the catalog, so they need no FULL build. A new builtin trips three guards at once — builtinModes, the skill's builtin list and its struct table — which is the checklist", "issue": "mendixlabs/mxcli#1306", "file": "mdl/linter/starlark.go (builtinLanguages); mdl/linter/context.go (LintReader)", "test": "mdl/linter/starlark_languages_test.go"} +{"area": "mdl/catalog", "date": "2026-10-07", "symptom": "activities_for() / CATALOG.ACTIVITIES returns every call and delete with its target empty: `MicroflowCallAction: action_ref=\"\" entity_ref=\"\" service_ref=\"\"` (also NanoflowCallAction, JavaActionCallAction, JavaScriptActionCallAction, DeleteObjectAction); no column says which call runs in a task queue; a nanoflow's JavaScript action call has no refs_from() row. refs_from() names the call/delete targets fine, and the lint run reports nothing.", "cause": "describeAction (builder_microflows.go) filled ActionRef/EntityRef only for REST/web service/OData calls and create/retrieve; the call and delete cases were never written. The delete's entity was resolvable all along via buildVarEntityMap, which only the refs builder used. microflowActionRef had no JavaScriptActionCallAction case.", "file": "`mdl/catalog/builder_microflows.go` (describeAction, insertFlowActivities), `mdl/catalog/builder_references.go` (microflowActionRef), `mdl/catalog/tables.go` (QueueRef, schema 23)", "insight": "Two builders derive per-action facts from the same parsed actions -- the refs switch and the activities switch -- and drift independently: an action can have its target in refs and an empty column in activities. When a row field is empty, check whether the other switch already resolves it before writing new resolution; here the delete's entity reused buildVarEntityMap (params, create/retrieve outputs, loop iterators) per flow. The queue lives on MicroflowCall.QueueSettings.Queue (and JavaActionCallAction.QueueSettings), not a top-level Queue. A new activities column breaks hand-built `CREATE TABLE activities` fixtures in linter tests (COALESCE does not cover a missing column) and needs a CatalogSchemaVersion bump. Control: HEAD binary on the issue's repro prints exactly the reported empty rows.", "refs": ["mendixlabs/mxcli#1305", "mendixlabs/mxcli#1266", "mendixlabs/mxcli#1267"]} diff --git a/.claude/skills/mendix/json-structures-and-mappings/SKILL.md b/.claude/skills/mendix/json-structures-and-mappings/SKILL.md index f9ddce9016..c14537a3b5 100644 --- a/.claude/skills/mendix/json-structures-and-mappings/SKILL.md +++ b/.claude/skills/mendix/json-structures-and-mappings/SKILL.md @@ -525,6 +525,9 @@ Two things the range does **not** do: mapping that already returns one object it builds clean (`mx check`: 0 errors) and the activity **throws at runtime**. `mxcli check` refuses it, and `offset` on such a mapping, as **MDL-MAP04** — drop the range. + `check` only sees the script: an activity already in the model with this + shape (written by an older mxcli, or with `exec --no-check`) is reported by + `mxcli lint -p app.mpr -r MDL-MAP04`, under the same ID. ### Export to Mapping (entity → JSON) diff --git a/.claude/skills/mendix/odata-data-sharing/SKILL.md b/.claude/skills/mendix/odata-data-sharing/SKILL.md index 904b5582fc..51c2d3a39e 100644 --- a/.claude/skills/mendix/odata-data-sharing/SKILL.md +++ b/.claude/skills/mendix/odata-data-sharing/SKILL.md @@ -188,9 +188,9 @@ create published odata service ProductApi.ProductDataApi_v2 ( ODataVersion: OData4, ServiceName: 'ProductDataApi', Summary: 'Product API v2 - includes weight and tags', + Authentication: (basic), ... ) -authentication basic { publish entity ProductApi.ProductWithPriceAndTagsVE as 'Product' ( ReadMode: ReadFromDatabase, @@ -240,9 +240,9 @@ create published odata service ProductApi.ProductDataApi ( path: 'odata/productdataapi/v1/', version: '1.0.0', ODataVersion: OData4, - folder: 'Integration/APIs' + folder: 'Integration/APIs', + Authentication: (basic) ) -authentication basic { ... }; ``` diff --git a/.claude/skills/mendix/odata-data-sharing/reference/errors-and-auth.md b/.claude/skills/mendix/odata-data-sharing/reference/errors-and-auth.md index 5e9ae948c0..3bc28a616b 100644 --- a/.claude/skills/mendix/odata-data-sharing/reference/errors-and-auth.md +++ b/.claude/skills/mendix/odata-data-sharing/reference/errors-and-auth.md @@ -157,13 +157,20 @@ that exist in the published metadata. That is defence in depth, not a substitute whitelist — it constrains the *name*, not what you do with it. ## Authentication Methods, and the Cost of Basic Auth -A published service names one or more methods in the `authentication` clause: +A published service names one or more methods in its `Authentication` property, +in the order Studio Pro lists them; `none` turns authentication off: ```sql -authentication basic, session -authentication microflow ProductApi.Authenticate +Authentication: (basic, session) +Authentication: (microflow ProductApi.Authenticate) +Authentication: none ``` +`alter published odata service M.S set ( Authentication: (session) )` changes it +on an existing service; left out of `create or modify`, the stored setting is kept. +The trailing `authentication basic, session` clause is the old spelling +(MDL-DEPR139) — `mxcli fmt --upgrade` moves it into the list. + | Method | How the caller proves itself | Cost per request | |---|---|---| | `basic` | `Authorization: Basic …` on every request | **a full password hash** | @@ -202,8 +209,7 @@ BEGIN RETURN $User; END; -create published odata service ProductApi.Api ( ... ) -authentication microflow ProductApi.Authenticate +create published odata service ProductApi.Api ( ..., Authentication: (microflow ProductApi.Authenticate) ) { ... }; ``` @@ -214,7 +220,7 @@ calling BCrypt. Two build rules to know before you reach for it: -- **The microflow is mandatory.** `authentication microflow` with no name parses +- **The microflow is mandatory.** `microflow` with no name parses but fails the build with **CE0333** "Please select a microflow to use for authentication". `mxcli check` flags this as `MDL-ODATA04`. - **App security must be on.** With security off, Mendix reports **CE6600** diff --git a/.claude/skills/mendix/odata-data-sharing/reference/walkthroughs.md b/.claude/skills/mendix/odata-data-sharing/reference/walkthroughs.md index 640622ca91..0eb3594d68 100644 --- a/.claude/skills/mendix/odata-data-sharing/reference/walkthroughs.md +++ b/.claude/skills/mendix/odata-data-sharing/reference/walkthroughs.md @@ -121,14 +121,14 @@ create published odata service ProductApi.ProductDataApi ( ODataVersion: OData4, namespace: 'DefaultNamespace', ServiceName: 'ProductDataApi', - Summary: 'Product and customer data API' + Summary: 'Product and customer data API', -- PublishAssociations is left at its default (Yes = associations as links). -- Setting it to No means "associations as an associated object id", which -- Mendix only allows when the system ID is published as the key — publishing -- an ordinary attribute as the key then fails the build with CE7375, even -- when no associations are exposed at all. + Authentication: (basic) ) -authentication basic { publish entity ProductApi.ProductWithPriceVE as 'Product' ( ReadMode: ReadFromDatabase, @@ -347,9 +347,9 @@ create published odata service Api.LapApi ( path: 'odata/laps/', version: '1.0.0', ODataVersion: OData4, - namespace: 'Api.Laps' + namespace: 'Api.Laps', + Authentication: (basic) ) -authentication basic { publish entity Api.Lap as 'Laps' ( ReadMode: microflow Api.Read_Laps, @@ -678,8 +678,7 @@ An entity set is a *read* surface. To let a client **invoke** something — Mendix exposes it in `$metadata` as an `ActionImport`. ```sql -create published odata service ProductApi.Actions ( ... ) -authentication basic +create published odata service ProductApi.Actions ( ..., Authentication: (basic) ) { publish microflow ProductApi.RecordNote as 'RecordNote' expose ( Note as 'note', Amount as 'amount' (CanBeEmpty) ); diff --git a/.claude/skills/mendix/write-lint-rules/SKILL.md b/.claude/skills/mendix/write-lint-rules/SKILL.md index 3b729520c7..e1e97919a8 100644 --- a/.claude/skills/mendix/write-lint-rules/SKILL.md +++ b/.claude/skills/mendix/write-lint-rules/SKILL.md @@ -80,6 +80,7 @@ Without this, a rule that reads a full-only table under a fast build gets | `module_roles()` | list of module_role | All module roles (deduplicated from role mappings) | | `role_mappings()` | list of role_mapping | User role to module role assignments | | `project_security()` | project_security or None | Project-level security settings (requires MPR reader) | +| `languages()` | list of language | The languages **enabled** in the project settings, in settings order (requires MPR reader; `[]` without one). Use it to scope per-language checks: `strings()` has a row for every stored translation, including languages the project never enabled | | `xpath_expressions()` | list of xpath_expression | All XPath constraint expressions in the catalog (access rules, retrieve actions, widgets) (full catalog — auto-detected) | | `modules()` | list of module | The user's modules (not System, not Marketplace), with their domain model's documentation | | `associations()` | list of association | All non-system associations, same-module and cross-module, with the delete behaviour of both ends | @@ -448,13 +449,14 @@ def count_not(node): | `auto_generate_caption` | bool | Action activity: whether Studio Pro generates the caption. False for other objects | | `description` | string | The documentation of an action activity, split or loop | | `activity_type` | string | `"ActionActivity"`, `"ExclusiveSplit"`, `"ExclusiveMerge"`, `"LoopedActivity"`, `"InheritanceSplit"`, `"StartEvent"`, `"EndEvent"`, `"Annotation"` | -| `action_type` | string | The action inside an `ActionActivity`: `"CreateObjectAction"`, `"ChangeObjectAction"`, `"CommitObjectsAction"`, `"DeleteObjectAction"`, `"RetrieveAction"`, `"MicroflowCallAction"`, `"ShowPageAction"`, `"ClosePageAction"`, `"LogMessageAction"`, `"JavaActionCallAction"`, `"RestCallAction"`, `"WebServiceCallAction"`. Empty for an activity that is not an action | +| `action_type` | string | The action inside an `ActionActivity`: `"CreateObjectAction"`, `"ChangeObjectAction"`, `"CommitObjectsAction"`, `"DeleteObjectAction"`, `"RetrieveAction"`, `"MicroflowCallAction"`, `"NanoflowCallAction"`, `"ShowPageAction"`, `"ClosePageAction"`, `"LogMessageAction"`, `"JavaActionCallAction"`, `"JavaScriptActionCallAction"`, `"RestCallAction"`, `"WebServiceCallAction"`. Empty for an activity that is not an action | | `microflow_id` | string | Parent microflow UUID | | `microflow_qualified_name` | string | `"Sales.ACT_Customer_Create"` | | `module_name` | string | `"Sales"` | -| `entity_ref` | string | Entity qualified name, for a create object and a database retrieve | +| `entity_ref` | string | Entity qualified name, for a create object, a database retrieve, and a delete — the entity of the deleted variable when the flow types it (a parameter, a create or retrieve output, a loop iterator); empty when it cannot | | `service_ref` | string | Called service document (REST / web service / OData client); empty when the activity calls none | -| `action_ref` | string | Operation or action within that service; empty when the activity calls none | +| `action_ref` | string | Operation or action within that service; for a microflow, nanoflow, Java action or JavaScript action call, the called document, e.g. `"Sales.SUB_Process"` (`service_ref` then empty). Empty when the activity calls nothing | +| `queue_ref` | string | Microflow or Java action call run in a task queue: the queue, e.g. `"Sales.OrderQueue"`. The call runs asynchronously, outside the loop and transaction it appears in. Empty for a call that runs in place | | `use_request_timeout` | bool | Call REST service or Call web service: whether "Use a timeout" is enabled. False for other action types | | `timeout_expression` | string | Call REST service or Call web service: the timeout in seconds, stored as an expression, e.g. `"300"` | | `parent_loop_id` | string | `id` of the loop the activity is inside; empty at the top level. Only set with `activities_for(…, nested = True)` | @@ -541,7 +543,7 @@ Returned by `permissions()` (all types) or `permissions_for()` (entity-specific) | `source_type` | string | The document the edge comes FROM, upper-case: `"MICROFLOW"`, `"NANOFLOW"`, `"RULE"`, `"PAGE"`, `"SNIPPET"`, `"ENTITY"`, `"ASSOCIATION"`, `"WORKFLOW"`, `"NAVIGATION"`, `"SCHEDULED_EVENT"`, `"PUBLISHED_REST_OPERATION"`, `"PROJECT_SETTINGS"`, `"IMPORT_MAPPING"`, `"EXPORT_MAPPING"` | | `source_id` | string | Source UUID | | `source_name` | string | `"Sales.ACT_Customer_Create"` | -| `target_type` | string | What it points AT, upper-case: `"ENTITY"`, `"ASSOCIATION"`, `"MICROFLOW"`, `"NANOFLOW"`, `"RULE"`, `"PAGE"`, `"LAYOUT"`, `"WORKFLOW"`, `"WIDGET"`, `"JAVA_ACTION"`, `"REST_OPERATION"`, `"REGULAR_EXPRESSION"`, `"ATTRIBUTE"`, `"ENUMERATION"`, `"ENUMERATION_VALUE"`. `LAYOUT`, `WIDGET`, `ATTRIBUTE`, `ENUMERATION` and `ENUMERATION_VALUE` are only ever targets; `SCHEDULED_EVENT` and `PROJECT_SETTINGS` only ever sources | +| `target_type` | string | What it points AT, upper-case: `"ENTITY"`, `"ASSOCIATION"`, `"MICROFLOW"`, `"NANOFLOW"`, `"RULE"`, `"PAGE"`, `"LAYOUT"`, `"WORKFLOW"`, `"WIDGET"`, `"JAVA_ACTION"`, `"JAVASCRIPT_ACTION"`, `"REST_OPERATION"`, `"REGULAR_EXPRESSION"`, `"ATTRIBUTE"`, `"ENUMERATION"`, `"ENUMERATION_VALUE"`. `LAYOUT`, `WIDGET`, `ATTRIBUTE`, `ENUMERATION` and `ENUMERATION_VALUE` are only ever targets; `SCHEDULED_EVENT` and `PROJECT_SETTINGS` only ever sources | | `target_id` | string | Target UUID | | `target_name` | string | `"Sales.Customer"`; three-part for an attribute or an enumeration value: `"Sales.Order.Total"`, `"Sales.OrderStatus.Open"` | | `ref_kind` | string | How it references: `"call"`, `"create"`, `"retrieve"`, `"change"`, `"delete"`, `"commit"` (a commit action, or a create/change that commits — beside its `"create"`/`"change"` edge; a commit of a variable whose entity the flow cannot tell has no edge), `"show_page"`, `"datasource"`, `"action"`, `"layout"`, `"parameter"`, `"return"`, `"generalize"`, `"associate"`, `"home_page"`, `"login_page"`, `"menu_item"`, `"calculate"`, `"schedule"`, `"validate"`, `"settings"`, `"widget"`, `"sync"`, `"publish"`, `"event"`, `"member"` (binds/reads/writes an attribute or navigates an association), `"xpath"` (an XPath constraint names it), `"type"` (typed as an enumeration), `"value"` (an expression names an enumeration value), `"mapping"` (an import/export mapping maps the entity) — lower-case, unlike the types above. Attribute names used only through a variable in a free-text expression (`$Order/Total`) have no edge | @@ -571,6 +573,16 @@ Returned by `project_security()`. Returns `none` if no MPR reader is available. | `require_mixed_case` | bool | Must contain upper and lower case | | `require_symbol` | bool | Must contain a symbol | +### language + +Returned by `languages()`, one per language enabled in the project settings. A per-language check skips `strings()` rows whose `language` is not among these codes. + +| Property | Type | Description | +|----------|------|-------------| +| `code` | string | Language code: `"en_US"`, `"nl_NL"` | +| `is_default` | bool | Whether this is the project's default language | +| `check_completeness` | bool | Whether Studio Pro checks this language's translations for completeness | + ## Helper Functions | Function | Description | diff --git a/CHANGELOG.md b/CHANGELOG.md index cf9f1a5038..24fa179445 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Added +- **`languages()` in Starlark lint rules — the languages enabled in the project** (mendixlabs/mxcli#1306) — a rule had no way to tell which languages are enabled: `strings()` has a row for every stored translation, and a fresh en_US-only app already carries nl_NL texts, so enabling a language changed nothing a rule could see, and `languages()` failed to load with `undefined: languages`. It returns one `language` per enabled language, from the project settings, with `code`, `is_default` and `check_completeness`, so a per-language check (every page title translated into each enabled language) can filter `strings()` to the enabled codes. It reads the project settings, not the catalog, so it needs no full catalog build; a failed settings read fails the rule instead of answering `[]`. - **A published REST service's authentication** (mendixlabs/mxcli#1331) — `create [or modify] published rest service M.Api (…, Authentication: (basic, session, microflow M.Authenticate))` sets Studio Pro's **Requires authentication** and its methods: `basic` (username and password), `session` (active session), `microflow M.F` (custom); `Authentication: none` is "Requires authentication: No". The methods are stored in the order written, which is how Studio Pro stores the ones ticked, and `describe` prints them in the stored order (it omits `none`). Left out, `create or modify` and `alter` keep the stored setting, as before. `alter published rest service M.Api set ( Key: value, … )` now takes create's property list (`Path`, `Version`, `ServiceName`, `Authentication`); `set Key = '…'` still works. The authentication microflow must return `System.User` and take only a `System.HttpRequest` and/or `System.HttpResponse` (Mendix: CE0334, CE0336, measured with `mx check` on 11.14.0); `exec` and `check --references` refuse anything else as **MDL-REST04**. Executing the `describe` output of each of ako/TestApp's Studio Pro services writes nothing. - **`send email` — the built-in Send Email activity (Mendix 11.13+, beta)** — `send email ( From: …, To: …, Subject: 'Order {1}' with ({1} = …), Body: template '…', HtmlBody: template '…', Headers: ('X-Name': 'value'), Attachment: $Doc, Host: …, Port: …, SecurityType: ssl, CheckServerIdentity: true, ConnectionTimeout: 30000, Authentication: basic (Username: …, Password: …) ) [on error …];` creates a `Microflows$SendEmailAction`, which sends SMTP mail without the Email Connector module. The settings are one property list (ADR-0013), keyed by the metamodel's names; an unknown, repeated or misshapen key is an error, and so is a missing From, Host, Port or recipient (mxbuild: CE0166). `describe microflow` renders the activity instead of `-- Unsupported action: Microflows$SendEmailAction`, and the description re-executes to the same activity. `check` type-checks its expressions (E009: String addresses, host and credentials, Integer/Long port, String template parameters — what mxbuild reports as CE9528/CE0117), and warns on a server-identity check without SSL and on header names Studio Pro would refuse (MDL-EMAIL02/03). A 10.x–11.12 project is refused; a stored activity MDL cannot restate (authentication document, pre-11.13 message) still describes as unsupported rather than being rewritten smaller. `mxcli syntax microflow.send-email`. (mendixlabs/mxcli#1315) - **`mxcli playwright check` — a text verdict for pages of a running app, in one call** — `mxcli playwright check /p/a /p/b -p app.mpr` loads each page in one headless browser and prints the verdict `run --page-check` prints (title, heading, rows, visible text, error banners, console errors) plus failed same-origin requests and the HTTP status, then `OK n page(s)` or `FAIL k of n page(s)`. **Exit status** 0 all passed, 1 a page failed (HTTP error, sign-in form or a 401 instead of the page, an error banner or error dialog, a console error, a failed request, a failed assertion), 2 the check could not run. It signs in when needed — `--user/--password`, `--role R` (the project's demo user with that user role), or with only `-p` a demo user — saves the session under `.mxcli/playwright-check/`, reuses it on the next check and renews it when the runtime has restarted. `--assert-text`, `--assert-count 'SELECTOR>=N'`, and `--screenshot out.png`, which prints the path and never the image. It replaces the hand-written playwright-cli login/goto/sleep/eval/screenshot sequences that were about a sixth of the tool calls in a measured app-building session, each ending in a PNG read; the `test-app` and `verify-in-runtime` skills and `/mendix:test` now route "check a page" to it. @@ -17,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Changed +- **A published OData service's authentication is a property** — `create published odata service M.S ( …, Authentication: (basic, session, microflow M.Authenticate) )`, in the order written, replaces the trailing `authentication basic, session` clause (R9). The property can also say `Authentication: none`, which the clause could not, and `alter published odata service M.S set ( Authentication: … )` now changes authentication on an existing service — before, it could only be restated with the whole service. Left out, `create or modify` and `alter` keep the stored setting, as before. `describe` prints the property. **Migrating a script:** nothing breaks — the clause (**MDL-DEPR139**) still parses and builds the same service, `check` / `exec` warn, and `mxcli fmt --upgrade` moves it into the list (a clause naming a method MDL has no keyword for, e.g. `authentication Custom`, is reported and left alone). A stored setting MDL cannot state — a microflow stored without the Microflow method, or the reverse — is now a comment in `describe` rather than printed as `Microflow M.F`, which used to add the method when the output was executed. Executing the `describe` output of each of ako/TestApp's three Studio Pro OData services writes nothing. - **`call rest service` takes its settings as one property list** (ADR-0013) — the activity's dialog settings go in one `( Key: value, … )` list after the URL, keyed as the consumed REST service names the same concepts: `$Html = call rest service get 'https://example.com' (Headers: ('Accept': 'text/html'), Authentication: basic (Username: $User, Password: $Password), Timeout: 300) returns String;`. `Body:` is `template '…' [with ({1} = …)]`, `mapping M.EMM from $Var`, `binary ` or an expression. The method, URL, `returns …` and `on error …` stay words. An unknown or repeated key, or a value of the wrong shape, is an error. `describe` writes this form. **Migrating a script:** nothing breaks — the clauses `header 'N' = v`, `auth basic $u password $p`, `body …` and `timeout n` (**MDL-DEPR720**) still parse and store the same activity, `check` / `exec` warn, and `mxcli fmt --upgrade` rewrites them; a statement cannot mix the two forms. ADR-0013 makes this the rule for every new microflow activity and every activity with several settings. - **`run --local --page-check` signs in with `--screenshot-user` without `--screenshot`, and checks all pages in one browser** — the sign-in only ran when `--screenshot` was also given, so `--page-check --screenshot-user U` reported every secured page as the login page. The verdict also no longer counts a list view's "No items found" placeholder or a data grid's header as rows, ignores the demo-user switcher's "Select user" heading, and reports a failed same-origin request (`HTTP 560 POST /xas/`) instead of the duplicate "Failed to load resource" console line. The login script used by `--screenshot-user` falls back to `/login.html` when the app root does not show the sign-in form, and finds Playwright the way the page check does (no `playwright` CLI on `PATH` needed). @@ -24,6 +26,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Fixed +- **The catalog names a call's and a delete's target** (mendixlabs/mxcli#1305) — `activities_for()` and `CATALOG.ACTIVITIES` returned every microflow, nanoflow, Java action and JavaScript action call with `action_ref=""`, and every delete with `entity_ref=""`, although `refs_from()` had both targets; a loop-scoped lint rule could not follow a call out of the loop. `action_ref` / `ActionRef` is now the called document, `entity_ref` / `EntityRef` a delete's entity (when the flow types the variable: a parameter, a create or retrieve output, a loop iterator), and the new `queue_ref` / `QueueRef` the task queue a microflow or Java action call runs in, so a rule can skip a call that runs asynchronously. A nanoflow's JavaScript action call now has a `refs_from()` `call` row (`target_type` `"JAVASCRIPT_ACTION"`), so `show callers` sees it. The catalog schema is bumped to 23; a cached catalog rebuilds. +- **A DataGrid 2 column's `Visible:` expression is written** — `Visible: $showPrices`, `Visible: if … then … else …`, `visible: not(…)` and `Visible: [cond]` on a column passed `check` and `exec` and were stored as `true`, so the column was always visible; only the old quoted `Visible: ''` was kept. `alter page … set (Visible: ) on grid column(…)` was refused as "column property VisibleIf not found", and an inserted column dropped `Visible: false` too. `describe` now prints the bare expression. A column's visibility is evaluated once for the grid, with no row object, so `$currentObject` there is CE0117 at build; `check` refuses it as **MDL-WIDGET43** (measured on 11.14.0). Use a page variable or parameter. Projects regenerate their widget definitions (generator version 18). - **`set $Param = …` on a parameter is refused** — a Change variable cannot target a parameter, and mxbuild rejects it with CE7247 "Parameter 'N' cannot be changed." (measured on 11.14.0 for Integer and String parameters in a microflow, a nanoflow and a rule). `check` and `exec` passed it. MDL-SET01 now refuses it for every parameter but a list (`set` on a list parameter is a Change list Replace, which builds), and `exec` enforces the rule. Copy the parameter into a variable first: `declare $Value Integer = $N;`. - **`check` refuses a button that passes its own data container by widget name** (mendixlabs/mxcli#1324) — `actionbutton btnOwn (Action: call microflow M.F(Gate = $dvGate))` directly inside data view `dvGate` passed `check --references` and `exec`, then `mx check` reported `[CE0117] "Error(s) in expression." at Action button 'btnOwn'`. A data container's name is a variable only for the containers nested below it; in its own context the object is `$currentObject`. Reported as **MDL-BUTTON02** (error, so `exec` refuses it), for data views, list views, galleries and data grids alike, including attribute paths (`$dvGate/Name`) and a control bar inside the container. The rule covers every slot evaluated in that context, not only action arguments: a nested widget's microflow data-source arguments, `Visible:`, `Editable:` and `DynamicClasses:` (CE0117), and a nested list's XPath `where` (CE0161). A grid's own name from its control bar (the selection) and an enclosing container's name from a nested one are not flagged. The flagged set matches mxbuild 11.14.0's CE0117s widget for widget on a ten-button probe page. - **`set $Obj = …` on an object variable is refused** (mendixlabs/mxcli#1323) — with both variables single objects (e.g. a Reference retrieved from its FROM entity), `set $Cursor = $Next;` passed `check --references` and `exec` and was written as a Change variable action, which mxbuild refuses with CE7247 "Variable 'Cursor' does not have a primitive type". Mendix has no action that reassigns an object variable, so `check` (MDL-SET01, for the objects it can see without a project), `check --references` and `exec` now refuse it and name the alternatives (a sub-microflow that returns the next object, `change $Obj (…)`). `check --references` now types a Reference retrieve from its FROM entity as the object it is. `set` on a list variable stays a Change list Replace (ako/mxcli#949). diff --git a/cmd/mxcli/cmd_lint.go b/cmd/mxcli/cmd_lint.go index c1d41be4c1..4a62794adc 100644 --- a/cmd/mxcli/cmd_lint.go +++ b/cmd/mxcli/cmd_lint.go @@ -430,6 +430,7 @@ func builtinLintRules() []linter.Rule { rules.NewExclusiveSplitCaptionRule(), rules.NewErrorHandlingOnCallsRule(), rules.NewNoContinueErrorHandlingRule(), - rules.NewIrreducibleFlowGraphRule(), // MDL-FLOW01 - graph structure vs MDL nesting + rules.NewIrreducibleFlowGraphRule(), // MDL-FLOW01 - graph structure vs MDL nesting + rules.NewImportRangeObjectMappingRule(), // MDL-MAP04 - First over an object-rooted mapping in the model } } diff --git a/cmd/mxcli/syntax/features_integration.go b/cmd/mxcli/syntax/features_integration.go index 6b67f0c38c..4b91df2af8 100644 --- a/cmd/mxcli/syntax/features_integration.go +++ b/cmd/mxcli/syntax/features_integration.go @@ -90,18 +90,23 @@ func init() { " namespace: 'Module.Customers',\n" + " ServiceName: 'CustomerApi', -- optional; defaults to the document name\n" + " PublishAssociations: Yes, -- optional; default Yes (associations as links)\n" + - " SupportsGraphQL: Yes -- optional; also answer GraphQL at the SAME\n" + + " SupportsGraphQL: Yes, -- optional; also answer GraphQL at the SAME\n" + " -- location (POST a query). Mendix 10.14+.\n" + " -- Exposed names must then be unique beyond\n" + " -- case (CE2881), and query fields are\n" + " -- camelCased: Period -> period\n" + + " Authentication: (basic, session) -- the methods, in order: basic, session,\n" + + " -- guest, microflow Module.Authenticate;\n" + + " -- `none` = no authentication. Left out,\n" + + " -- CREATE OR MODIFY / ALTER keep the stored one.\n" + ")\n" + - "authentication basic, session\n" + - "-- or, for custom authentication (no per-request password hash):\n" + - "-- authentication microflow Module.Authenticate\n" + + "-- Custom authentication (no per-request password hash):\n" + + "-- Authentication: (microflow Module.Authenticate)\n" + "-- The microflow takes a List of System.HttpHeader and returns a\n" + "-- System.User; returning empty denies the request. Requires app\n" + "-- security to be on (CE6600) and a microflow to be named (CE0333).\n" + + "-- The trailing `authentication basic, session` clause is the old spelling\n" + + "-- (MDL-DEPR139); `mxcli fmt --upgrade` moves it into the list.\n" + "{\n" + " publish entity Module.Entity as 'EntitySet' (\n" + " ReadMode: source | microflow Module.Read_X,\n" + @@ -163,9 +168,9 @@ func init() { " path: 'odata/customers/',\n" + " version: '1.0.0',\n" + " ODataVersion: OData4,\n" + - " namespace: 'Shop.Customers'\n" + + " namespace: 'Shop.Customers',\n" + + " Authentication: (basic)\n" + ")\n" + - "authentication basic\n" + "{\n" + " publish entity Shop.Customer as 'Customers' (\n" + " ReadMode: source\n" + diff --git a/cmd/mxcli/syntax/features_microflow.go b/cmd/mxcli/syntax/features_microflow.go index 4a413b5ee5..ec85d1aced 100644 --- a/cmd/mxcli/syntax/features_microflow.go +++ b/cmd/mxcli/syntax/features_microflow.go @@ -806,7 +806,9 @@ func init() { "mapping binds an object under ALL too (Studio Pro's own default).\n" + "Mendix rejects OFFSET on a non-list mapping with CE6100, and FIRST on\n" + "an object-rooted mapping builds clean and throws at runtime — mxcli\n" + - "check refuses both as MDL-MAP04. Drop the range on such a mapping.", + "check refuses both as MDL-MAP04, and mxcli lint reports an activity\n" + + "already in the model that stores FIRST on such a mapping under the\n" + + "same ID. Drop the range on such a mapping.", Example: "$Pets = import from mapping Shop.IMM_Pets($Json) all;\n" + "$Pet = import from mapping Shop.IMM_Pets($Json) first;\n" + "$Page = import from mapping Shop.IMM_Pets($Json) limit 10 offset 5;\n" + diff --git a/cmd/mxcli/syntax/features_page.go b/cmd/mxcli/syntax/features_page.go index 0e4a53514e..b1d60a6bea 100644 --- a/cmd/mxcli/syntax/features_page.go +++ b/cmd/mxcli/syntax/features_page.go @@ -402,7 +402,7 @@ LIST IMPACT OF htmlelement; "column width", "alignment", "wrap text", "visible", "dynamic cell class", "tooltip", "associated attribute", "association column", }, - Syntax: "COLUMN name (\n Attribute: AttrName, -- own attribute\n -- or an attribute over an association (bare association name):\n -- Attribute: Assoc/Attr e.g. Order_Customer/Name\n Caption: 'Header'\n [, Sortable: true|false]\n [, Resizable: true|false]\n [, Draggable: true|false]\n [, Hidable: yes|hidden|no]\n [, ColumnWidth: autoFill|autoFit|manual]\n [, Size: integer]\n [, Alignment: left|center|right]\n [, WrapText: true|false]\n [, Visible: 'expression']\n [, DynamicCellClass: 'expression']\n [, Tooltip: 'text']\n)", + Syntax: "COLUMN name (\n Attribute: AttrName, -- own attribute\n -- or an attribute over an association (bare association name):\n -- Attribute: Assoc/Attr e.g. Order_Customer/Name\n Caption: 'Header'\n [, Sortable: true|false]\n [, Resizable: true|false]\n [, Draggable: true|false]\n [, Hidable: yes|hidden|no]\n [, ColumnWidth: autoFill|autoFit|manual]\n [, Size: integer]\n [, Alignment: left|center|right]\n [, WrapText: true|false]\n [, Visible: ] -- once for the grid: a page variable or parameter, never $currentObject (MDL-WIDGET43)\n [, DynamicCellClass: ] -- per row: $currentObject is the row\n [, Tooltip: 'text']\n)", Example: "COLUMN colPrice (\n Attribute: Price, Caption: 'Price',\n Alignment: right, Sortable: false,\n ColumnWidth: manual, Size: 150,\n Tooltip: 'Price in USD'\n)\n\n-- Associated attribute (attribute over a reference association):\nCOLUMN colCustomer (Attribute: Order_Customer/Name, Caption: 'Customer')", SeeAlso: []string{"page.widgets"}, }) diff --git a/docs-site/src/appendixes/quick-reference.md b/docs-site/src/appendixes/quick-reference.md index 3a1f6d5eaa..443a7d325d 100644 --- a/docs-site/src/appendixes/quick-reference.md +++ b/docs-site/src/appendixes/quick-reference.md @@ -125,9 +125,9 @@ CREATE PUBLISHED ODATA SERVICE MyModule.CustomerAPI ( Path: '/odata/customers', Version: '1.0.0', ODataVersion: OData4, - Namespace: 'MyModule.Customers' + Namespace: 'MyModule.Customers', + Authentication: (basic, session) ) -AUTHENTICATION Basic, Session { PUBLISH ENTITY MyModule.Customer AS 'Customers' ( ReadMode: SOURCE, @@ -385,8 +385,8 @@ MDL uses explicit property declarations for pages: | `Hidable` | `yes`, `hidden`, `no` | `yes` | `Hidable: no` | | `ColumnWidth` | `autoFill`, `autoFit`, `manual` | `autoFill` | `ColumnWidth: manual` | | `Size` | integer (px) | `1` | `Size: 200` | -| `Visible` | expression string | `true` | `Visible: '$showColumn'` (page variable, not $currentObject) | -| `DynamicCellClass` | expression string | (empty) | `DynamicCellClass: if(...) then ... else ...` | +| `Visible` | expression | `true` | `Visible: $showColumn` — evaluated once for the grid: a page variable or parameter, never `$currentObject` (MDL-WIDGET43, CE0117) | +| `DynamicCellClass` | expression | (empty) | `DynamicCellClass: if $currentObject/Stock < 10 then 'text-danger' else ''` | | `Tooltip` | text string | (empty) | `Tooltip: 'Price in USD'` | **Page Example:** diff --git a/docs-site/src/internals/catalog-schema.md b/docs-site/src/internals/catalog-schema.md index 0bc5f7de54..587000f818 100644 --- a/docs-site/src/internals/catalog-schema.md +++ b/docs-site/src/internals/catalog-schema.md @@ -193,10 +193,10 @@ CREATE TABLE activities_data ( MicroflowQualifiedName TEXT, ModuleName TEXT, Folder TEXT, - EntityRef TEXT, -- create object / database retrieve entity + EntityRef TEXT, -- create object / database retrieve / delete entity ActionType TEXT, -- e.g. "RetrieveAction", "MicroflowCallAction" ServiceRef TEXT, -- called service (REST, web service, OData) - ActionRef TEXT, -- operation within it + ActionRef TEXT, -- operation within it, or the called microflow/nanoflow/Java/JavaScript action UseRequestTimeout INTEGER, TimeoutExpression TEXT, Description TEXT, -- documentation @@ -212,6 +212,7 @@ CREATE TABLE activities_data ( CommitType TEXT, -- Yes / YesWithoutEvents / No WithEvents INTEGER, RetrieveSource TEXT, -- database / association + QueueRef TEXT, -- task queue a microflow/Java action call runs in ProjectId TEXT, SnapshotId TEXT ); diff --git a/docs-site/src/language/versions.md b/docs-site/src/language/versions.md index 700ca31200..a66e7c2ad5 100644 --- a/docs-site/src/language/versions.md +++ b/docs-site/src/language/versions.md @@ -255,7 +255,7 @@ refuse the spelling; until then it only warns. ### Deprecated spellings (`MDL-DEPR*`) -86 old spellings mean exactly what their new form means. They warn with their code under every version before the one in the last column, which refuses them. +87 old spellings mean exactly what their new form means. They warn with their code under every version before the one in the last column, which refuses them. | Code | Old form | New form | Rewritten by `fmt --upgrade` | Refused from | |---|---|---|---|---| @@ -323,6 +323,7 @@ refuse the spelling; until then it only warns. | `MDL-DEPR136` | `create constant M.C type T default v [exposed to client]` | `create constant M.C ( Type: T, DefaultValue: v, ExposedToClient: true )` | yes: constant properties: `type T default v exposed to client` becomes `( Type: T, DefaultValue: v, ExposedToClient: true )` | mdl 2 | | `MDL-DEPR137` | `create demo user 'u' password 'p' [entity M.E] (Role, …)` | `create demo user 'u' ( Password: 'p', Entity: M.E, UserRoles: (Role, …) )` | yes: demo user properties: `password 'p' entity M.E (R1, R2)` becomes `( Password: 'p', Entity: M.E, UserRoles: (R1, R2) )` | mdl 2 | | `MDL-DEPR138` | `create constant M.C … private` | `create constant M.C …` | yes: constant's `private` modifier away: it is deleted | mdl 1 | +| `MDL-DEPR139` | `create published odata service M.S ( … ) authentication basic, session, microflow M.F` | `create published odata service M.S ( …, Authentication: (basic, session, microflow M.F) )` | yes: published OData authentication: the trailing `authentication m1, m2` clause becomes the last property, `Authentication: (m1, m2)` | mdl 2 | | `MDL-DEPR140` | `alter workflow M.W set display 'x' / set description … / set export level … / set due date … / set overview page … / set parameter $P: M.E` | `alter workflow M.W { set ( Display: 'x', Description: …, ExportLevel: …, DueDate: …, OverviewPage: …, Parameter: $P: M.E ); }` | yes: property as `set ( Key: value )`, inside the statement's { } | mdl 2 | | `MDL-DEPR141` | `alter workflow M.W set activity X page M.P / description … / targeting … / due date …` | `alter workflow M.W { set ( Page: M.P, Description: …, Targeting: …, DueDate: … ) on X; }` | yes: `set activity X v` as `set ( Key: v ) on X`, inside the statement's { } | mdl 2 | | `MDL-DEPR142` | `alter workflow M.W insert after X ;` | `alter workflow M.W { insert after X { ; } }` | yes: inserted activity in { }, inside the statement's { } | mdl 2 | diff --git a/docs-site/src/tools/builtin-rules.md b/docs-site/src/tools/builtin-rules.md index 2789a9d7c9..8a611b1c72 100644 --- a/docs-site/src/tools/builtin-rules.md +++ b/docs-site/src/tools/builtin-rules.md @@ -39,6 +39,7 @@ The **lint rules** below run with `mxcli lint`. There is also a separate group o | **CONV013** | Error handling on external calls -- Ensures external service calls have error handling | | **CONV014** | No continue error handling -- Warns against using CONTINUE error handling without logging | | **MDL-FLOW01** | Un-describable branch structure -- Decision branches that re-enter each other's paths, so `DESCRIBE MICROFLOW` cannot render them as nested `IF`s without changing what they mean | +| **MDL-MAP04** | First over an object-rooted import mapping -- An `import from mapping` or `rest call … returns mapping` activity already in the model stores Studio Pro's *First* (`ForceSingleOccurrence` or `Range.SingleObject`) over a mapping that returns one object. `mx check` reports 0 errors; the activity throws `key not found: Path(QName(None,),None,)` when it runs. `mxcli check` refuses the same statement in a script under the same ID; this finds activities written before that check existed, or with `exec --no-check`. A list-rooted mapping's *First* is legitimate and is not reported | ## Running Built-in Rules diff --git a/docs-site/src/tools/catalog-tables.md b/docs-site/src/tools/catalog-tables.md index 516c89671d..6ad769af9f 100644 --- a/docs-site/src/tools/catalog-tables.md +++ b/docs-site/src/tools/catalog-tables.md @@ -151,8 +151,9 @@ query written then that should keep its old result filters on | `Caption` | The stored caption: an activity's, a split's, or an annotation's text. Empty for events, merges and loops. With `AutoGenerateCaption` it holds Studio Pro's stored placeholder (often `Activity`) | | `AutoGenerateCaption` | 1 when Studio Pro generates the activity's caption | | `Description` | Documentation of an action activity, split or loop | -| `EntityRef` | Entity of a create object or a database retrieve | -| `ServiceRef`, `ActionRef` | Called service and operation: REST, web service, OData action | +| `EntityRef` | Entity of a create object, a database retrieve, or a delete (the deleted variable's entity, when the flow types it) | +| `ServiceRef`, `ActionRef` | Called service and operation: REST, web service, OData action. For a microflow, nanoflow, Java or JavaScript action call, `ActionRef` is the called document and `ServiceRef` is empty | +| `QueueRef` | Task queue a microflow or Java action call runs in; empty when it runs in place | | `UseRequestTimeout`, `TimeoutExpression` | "Use a timeout" and its seconds, for a REST or web service call | | `ConditionExpression` | An exclusive split's expression | | `ConditionRule` | The rule a rule-based split calls | @@ -167,6 +168,11 @@ query written then that should keep its old result filters on SELECT MicroflowQualifiedName, EntityRef, LoopDepth FROM CATALOG.ACTIVITIES WHERE ActionType = 'RetrieveAction' AND RetrieveSource = 'database' AND ParentLoopId <> ''; + +-- Microflows called from inside a loop, synchronously (a queued call runs outside it) +SELECT MicroflowQualifiedName, ActionRef +FROM CATALOG.ACTIVITIES +WHERE ActionType = 'MicroflowCallAction' AND ParentLoopId <> '' AND QueueRef = ''; ``` ### CATALOG.PAGES diff --git a/docs-wiki/bug-patterns/silent-property-drop.md b/docs-wiki/bug-patterns/silent-property-drop.md index 7ce6c63009..5c0caa1676 100644 --- a/docs-wiki/bug-patterns/silent-property-drop.md +++ b/docs-wiki/bug-patterns/silent-property-drop.md @@ -125,6 +125,16 @@ answer "this shape does not fit" (MDL-WIDGET42, and an error at build) instead o falling to `default: continue`. When a grammar alternative is chosen by the *first token*, audit which other meanings that token starts. +**One property, two keys: a consumer that reads one drops the other.** The +visitor lowers `Visible:` by value shape — an expression to `VisibleIf`, a plain +value to `Visible` — and the two writers each read only one: page widgets read +`VisibleIf` (and once dropped `Visible: false`), DataGrid 2 columns read +`Visible` (and dropped every expression). Grep the consumers of the *key the +visitor writes*, not of the property name. And expect persisting a value to +surface a rule its absence hid: a column's visibility has no row object, so the +`$currentObject` examples that had always "worked" became CE0117 the moment they +were written, and the fix needed a check rule to go with it. + **Children drop the same way properties do.** A widget's body is distributed by several passes that each skip what they do not recognise, so a child matching no container, no slot and no catch-all is built and discarded exactly as an diff --git a/docs/01-project/MDL_QUICK_REFERENCE.md b/docs/01-project/MDL_QUICK_REFERENCE.md index f79bff0fa7..e8df8a9cb4 100644 --- a/docs/01-project/MDL_QUICK_REFERENCE.md +++ b/docs/01-project/MDL_QUICK_REFERENCE.md @@ -430,15 +430,15 @@ create published odata service MyModule.CustomerAPI ( path: 'odata/customers/', -- no leading slash (CE6550); trailing slash required (CE6552) version: '1.0.0', ODataVersion: OData4, - namespace: 'MyModule.Customers' + namespace: 'MyModule.Customers', + Authentication: (basic, session) -- the methods in order; `none` = no authentication ) -authentication basic, session -- Inside the { } body, alongside `publish entity`, a microflow can be published -- as an OData action (an ActionImport in $metadata): -- publish microflow Module.DoThing as 'DoThing' -- expose ( Note as 'note', Amount as 'amount' (CanBeEmpty) ); -- Parameter types and the return type are read off the microflow, not restated. --- or: authentication microflow Module.Authenticate +-- or: Authentication: (microflow Module.Authenticate) -- Custom authentication. The microflow takes a List of System.HttpHeader and -- returns a System.User (empty denies). It removes the per-request password -- hash that `basic` pays on every call. Requires app security on (CE6600) and diff --git a/docs/11-proposals/PROPOSAL_published_rest_authentication.md b/docs/11-proposals/PROPOSAL_published_rest_authentication.md index 082dd71646..52049808ed 100644 --- a/docs/11-proposals/PROPOSAL_published_rest_authentication.md +++ b/docs/11-proposals/PROPOSAL_published_rest_authentication.md @@ -189,8 +189,9 @@ Both fields exist on every version mxcli supports (10.0+), so no version gate. ## Open Questions -1. Migrate the published OData `authentication` clause to an `Authentication:` - property (with the clause as a deprecated alias) so the two siblings agree. - Separate change. +1. ~~Migrate the published OData `authentication` clause to an `Authentication:` + property~~ — done as a separate change: the property, `none`, `alter … set + ( Authentication: … )`, and the clause as alias MDL-DEPR139 with an + `fmt --upgrade` rewrite. The two siblings now agree. 2. ~~Whether a service with roles but no authentication is accepted~~ — it is (Validation table). The reverse, authentication with no role, is CE0338. diff --git a/mdl-examples/bug-tests/1281-consecutive-xpath-step-predicates.mdl b/mdl-examples/bug-tests/1281-consecutive-xpath-step-predicates.mdl new file mode 100644 index 0000000000..16d2fd7d00 --- /dev/null +++ b/mdl-examples/bug-tests/1281-consecutive-xpath-step-predicates.mdl @@ -0,0 +1,48 @@ +mdl 1; +-- ============================================================================ +-- Bug: two predicates on one XPath step are a parse error (mendixlabs/mxcli#1281) +-- ============================================================================ +-- +-- Symptom: +-- retrieve $x from M.Location where +-- [M.UserAssignment_TenantUser/M.TenantUser[Status = 'Active'][AnonymizedAt = empty] +-- /M.TenantUser_Account = '[%CurrentUser%]']; +-- -> "mismatched input '/' expecting ';'" when the path continues past the +-- step, "missing ']' at '['" when it ends on it. Mendix accepts the form. +-- +-- Root cause: +-- xpathStep took at most one predicate: `xpathStepValue ([ xpathExpr ])?`. +-- +-- Fix: +-- `*` in the grammar; ast.XPathStep.Predicates holds each one in source +-- order, so the stored constraint keeps `[a][b]` rather than an `and`. +-- +-- Verify: +-- mxcli exec mdl-examples/bug-tests/1281-consecutive-xpath-step-predicates.mdl -p app.mpr +-- mxcli -p app.mpr -c "describe microflow BugTest1281.Mid" # both predicates kept +-- mx check app.mpr # 0 errors (measured on 11.6.6) +-- ============================================================================ + +create module BugTest1281; + +create persistent entity BugTest1281.Account (Name: String(100)); +create persistent entity BugTest1281.TenantUser (Status: String(20), AnonymizedAt: DateTime); +create persistent entity BugTest1281.Location (Name: String(100)); +create association BugTest1281.UserAssignment_TenantUser from BugTest1281.Location to BugTest1281.TenantUser; +create association BugTest1281.TenantUser_Account from BugTest1281.TenantUser to BugTest1281.Account; + +-- The path continues after the step with two predicates. +create microflow BugTest1281.Mid () +returns Nothing +begin + retrieve $Locations from BugTest1281.Location + where [BugTest1281.UserAssignment_TenantUser/BugTest1281.TenantUser[Status = 'Active'][AnonymizedAt = empty]/BugTest1281.TenantUser_Account/BugTest1281.Account/Name = 'x']; +end; + +-- The path ends on the step with two predicates. +create microflow BugTest1281.End () +returns Nothing +begin + retrieve $Locations from BugTest1281.Location + where [BugTest1281.UserAssignment_TenantUser/BugTest1281.TenantUser[Status = 'Active'][AnonymizedAt = empty]]; +end; diff --git a/mdl-examples/bug-tests/1282-java-action-microflow-arg-line-break.mdl b/mdl-examples/bug-tests/1282-java-action-microflow-arg-line-break.mdl new file mode 100644 index 0000000000..51c84f6b7a --- /dev/null +++ b/mdl-examples/bug-tests/1282-java-action-microflow-arg-line-break.mdl @@ -0,0 +1,46 @@ +mdl 1; +-- ============================================================================ +-- mendixlabs/mxcli#1282 — a Microflow-typed Java action argument followed by a +-- line break before the closing parenthesis +-- ============================================================================ +-- +-- Reported (mxcli 0.23/0.24, Mendix 11.12.4): check and exec passed, then +-- mx check failed with +-- [CE1613] "The selected microflow 'MyModule.SUB_Target⏎' no longer exists" +-- because the line break was stored as part of the reference. With the `)` +-- on the same line, mx check reported 0 errors. +-- +-- The visitor keeps whitespace before `)` on the argument (a SourceExpr), so an +-- expression argument round-trips as written. The Microflow-typed path must +-- read the name inside that wrapper: once #1210's guard existed, the wrapper +-- itself was refused as "not a microflow name". Both forms below must pass +-- and store exactly `BugFlowArgNl.SUB_Target`. +-- +-- Go tests: mdl/executor/javaaction_flow_param_whitespace_test.go and +-- TestCheckExecAgree_MicroflowTypedJavaActionParameter. +-- ============================================================================ + +create module BugFlowArgNl; + +create java action BugFlowArgNl.DoSomething (Flow: Microflow) returns Boolean +as $$ +return true; +$$; + +create microflow BugFlowArgNl.SUB_Target () returns Nothing +begin + log info 'x'; +end; + +-- The reported shape: `)` on the next line. +create microflow BugFlowArgNl.ACT_LineBreak () returns Nothing +begin + $ok = call java action BugFlowArgNl.DoSomething(Flow = BugFlowArgNl.SUB_Target + ); +end; + +-- Control: the same call with `)` on the same line. +create microflow BugFlowArgNl.ACT_SameLine () returns Nothing +begin + $ok = call java action BugFlowArgNl.DoSomething(Flow = BugFlowArgNl.SUB_Target); +end; diff --git a/mdl-examples/bug-tests/1305-catalog-call-delete-targets.mdl b/mdl-examples/bug-tests/1305-catalog-call-delete-targets.mdl new file mode 100644 index 0000000000..e4e3f09bb3 --- /dev/null +++ b/mdl-examples/bug-tests/1305-catalog-call-delete-targets.mdl @@ -0,0 +1,51 @@ +mdl 1; +-- Issue mendixlabs/mxcli#1305: the catalog's call and delete activities did not +-- name their target. activities_for(…, nested = True) returned +-- +-- Act_Loop MicroflowCallAction: action_ref="" entity_ref="" service_ref="" caption="" loop_depth=1 +-- Act_Loop DeleteObjectAction: action_ref="" entity_ref="" service_ref="" caption="" loop_depth=1 +-- +-- for every call and delete below, although refs_from() named the targets, and +-- a JavaScript action call had no refs_from() row at all. +-- +-- After `refresh catalog full` the activities table holds: +-- both calls ActionRef Bug1305.Sub_Process +-- the queued call QueueRef Bug1305.SaleQueue +-- the delete EntityRef Bug1305.Sale (from the loop iterator's type) +-- nanoflow call ActionRef Bug1305.Nav_Sub +-- JavaScript call ActionRef NanoflowCommons.ClearLocalStorage +-- and refs has a `call` row Nav_Loop -> JAVASCRIPT_ACTION. +-- +-- select ActionType, ActionRef, QueueRef, EntityRef from CATALOG.ACTIVITIES +-- where ParentLoopId <> '' and ModuleName = 'Bug1305'; +-- +-- NanoflowCommons ships with every new app (`mxcli new`). + +create or modify module Bug1305; + +create or modify persistent entity Bug1305.Sale (Amount: Integer); +create or modify task queue Bug1305.SaleQueue (Parallelism: 1); +create or modify microflow Bug1305.Sub_Process ($Sale: Bug1305.Sale) +begin + log info node 'Bug1305' 'processing'; +end; +create or modify microflow Bug1305.Act_Loop ($Sales: list of Bug1305.Sale) +begin + loop $Sale in $Sales + begin + call microflow Bug1305.Sub_Process (Sale = $Sale); + call microflow Bug1305.Sub_Process (Sale = $Sale) in queue Bug1305.SaleQueue; + delete $Sale; + end loop; +end; +create or modify nanoflow Bug1305.Nav_Sub () +begin +end; +create or modify nanoflow Bug1305.Nav_Loop ($Sales: list of Bug1305.Sale) +begin + loop $Sale in $Sales + begin + call nanoflow Bug1305.Nav_Sub (); + call javascript action NanoflowCommons.ClearLocalStorage (); + end loop; +end; diff --git a/mdl-examples/bug-tests/1306-lint-languages-builtin.mdl b/mdl-examples/bug-tests/1306-lint-languages-builtin.mdl new file mode 100644 index 0000000000..7eeb001651 --- /dev/null +++ b/mdl-examples/bug-tests/1306-lint-languages-builtin.mdl @@ -0,0 +1,21 @@ +mdl 1; +-- ============================================================================ +-- mendixlabs/mxcli#1306 — a Starlark lint rule could not read the enabled +-- project languages +-- ============================================================================ +-- +-- Reported: strings() returns rows for every stored translation, including +-- languages that are not enabled (a fresh en_US-only app already carries nl_NL +-- texts), so enabling nl_NL changed nothing a rule could see; a rule calling +-- languages() was skipped with "undefined: languages". +-- +-- The assertion is a Go test (mdl/linter/starlark_languages_test.go): the +-- symptom is in the lint API, which `make check-mdl` does not exercise. This +-- script is the model change from the report. To reproduce by hand on a fresh +-- `mxcli new` app, run it, then lint a rule that returns +-- [violation(message = l.code) for l in languages()] +-- before it, languages() lists en_US only; after it, en_US and nl_NL, while +-- strings() reports the same per-language counts both times. +-- ============================================================================ + +alter settings LANGUAGE add or modify 'nl_NL' (CheckCompleteness: true); diff --git a/mdl-examples/bug-tests/1319-lint-first-on-object-rooted-mapping.mdl b/mdl-examples/bug-tests/1319-lint-first-on-object-rooted-mapping.mdl new file mode 100644 index 0000000000..793c3f7c8f --- /dev/null +++ b/mdl-examples/bug-tests/1319-lint-first-on-object-rooted-mapping.mdl @@ -0,0 +1,70 @@ +mdl 1; +-- ============================================================================ +-- Bug #1319: lint missed import activities stored as First on an +-- object-rooted mapping +-- ============================================================================ +-- +-- SYMPTOM (reported on 11.14.0): an activity already in the model that stores +-- Studio Pro's First over an OBJECT-rooted import mapping — +-- ImportMappingCall.ForceSingleOccurrence = true, Range.SingleObject = true — +-- passed every gate: `mxcli lint` said "No issues found.", `mx check` said +-- "The app contains: 0 errors." The first sign was the activity throwing +-- +-- key not found: Path(QName(None,),None,) +-- +-- when the microflow ran. check's MDL-MAP04 (#570) refuses +-- `import from mapping … first` on such a mapping, but only in a SCRIPT; an +-- activity written by v0.24.0 or earlier, or with `exec --no-check`, was +-- found by nothing. +-- +-- FIX: `mxcli lint` runs MDL-MAP04 against the model too. It keys on the +-- MAPPING's root shape, not the flags: a LIST-rooted mapping stores the same +-- pair for Studio Pro's legitimate First, and is not reported. +-- +-- This script is the working shape: no range on the object-rooted mappings, +-- `first` / `all` only on the list-rooted one. To see the rule fire, change +-- MF_Order_Single's import to `… first;`, write it with +-- `mxcli exec … --no-check`, and run +-- +-- mxcli lint -p app.mpr -r MDL-MAP04 +-- +-- which reports MF_Order_Single and nothing else. +-- ============================================================================ + +create module G59; + +-- object-rooted JSON: {"number": ..., "lines": [ {"product": ...} ]} +create or modify non-persistent entity G59.OrderDoc ( Number: String(50) ); +create or modify non-persistent entity G59.OrderLine ( Product: String(50) ); +create or modify association G59.OrderLine_OrderDoc from G59.OrderLine to G59.OrderDoc; + +create or modify json structure G59.JSON_Order sample '{"number":"A-1","lines":[{"product":"P-1"}]}'; + +create or modify import mapping G59.IMM_Order with json structure G59.JSON_Order { + create G59.OrderDoc { Number = number, + create G59.OrderLine_OrderDoc/G59.OrderLine = lines { Product = product } } +}; + +-- list-rooted JSON (contrast): [ {"product": ...} ] +create or modify json structure G59.JSON_Lines sample '[{"product":"P-1"}]'; + +create or modify import mapping G59.IMM_Lines with json structure G59.JSON_Lines { + create G59.OrderLine { Product = product } +}; + +-- Object-rooted: no range. Stores both flags false, binds an object. +create or modify microflow G59.MF_Order_Single ($Json: String) +returns Boolean +begin + $Order = import from mapping G59.IMM_Order($Json); + return $Order != empty; +end; + +-- List-rooted: Studio Pro's legitimate First. Same stored flags as the +-- failing shape; lint must leave it alone. +create or modify microflow G59.MF_Lines_First ($Json: String) +returns Boolean +begin + $Line = import from mapping G59.IMM_Lines($Json) first; + return $Line != empty; +end; diff --git a/mdl-examples/bug-tests/1322-validation-feedback-bare-association.mdl b/mdl-examples/bug-tests/1322-validation-feedback-bare-association.mdl new file mode 100644 index 0000000000..39396cd154 --- /dev/null +++ b/mdl-examples/bug-tests/1322-validation-feedback-bare-association.mdl @@ -0,0 +1,27 @@ +mdl 1; +-- mendixlabs/mxcli#1322: validation feedback on an association written by its +-- BARE name was stored as an attribute reference. +-- +-- [CE1613] The selected attribute 'G45.Input.Input_Person' no longer exists. +-- +-- The bare name must be stored as the association G45.Input_Person — the same +-- BSON the qualified spelling ($Input/G45.Input_Person) produces. The attribute +-- (Note) next to it stays an attribute. + +create module "G45"; +create persistent entity "G45"."Person" ("Name": String(50)); +create non-persistent entity "G45"."Input" ("Note": String(50)); +create association "G45"."Input_Person" from "G45"."Input" to "G45"."Person" type Reference; +create microflow "G45"."VAL_Input" ($Input: "G45"."Input") returns Boolean as $Ok +begin + declare $Ok Boolean = true; + if $Input/G45.Input_Person = empty then + validation feedback $Input/Input_Person message 'Pick a person.'; + set $Ok = false; + end if; + if $Input/Note = empty then + validation feedback $Input/Note message 'Add a note.'; + set $Ok = false; + end if; + return $Ok; +end; diff --git a/mdl-examples/bug-tests/1325-retrieve-operand-types.mdl b/mdl-examples/bug-tests/1325-retrieve-operand-types.mdl new file mode 100644 index 0000000000..c9e08239a2 --- /dev/null +++ b/mdl-examples/bug-tests/1325-retrieve-operand-types.mdl @@ -0,0 +1,56 @@ +mdl 1; +-- mendixlabs/mxcli#1325 — a retrieve constraint comparing an attribute with a +-- variable of another type passed `check --references` and exec, then mx check +-- reported [CE0161] "Error(s) in XPath constraint." at the retrieve activity. +-- +-- Measured on mxbuild 11.14.0, `=` and `>=` alike. These are rejected, and +-- `mxcli check