Repository navigation
Sync ako/mxcli: mxcli git merge (#1674) with --plan over the engine's result, Concord dropped for Studio Pro's MCP, SEC011, MPR012 - #1714
Merged
Merged
Conversation
#1753) Symptom, from the issue: "On the after-startup test runner, a test with more than one `@expect` is reported **PASS** when an earlier assertion fails and a later one passes. `mxcli test` exits 0." Affected: every Docker run and `--local --legacy-runner`; the endpoint runner is not. Cause: writeExpectAssertion logs one verdict line per assertion, MXTEST:PASS:<id> or MXTEST:FAIL:<id>:..., and ParseLogResults let every line overwrite the test's status. The last line won, and the PASS case also cleared the failure message. The generated flow returned $AllPassed = false, but nothing reads it. Fix: the parser folds a test's verdict lines with a sticky failure (applyVerdictLine). Once a test has a FAIL or an ERROR, no later PASS or SKIP line clears it. The first failure's message is reported, with "(and N more failure(s) in this test)" when more followed. This matches the endpoint runner, which evaluates an assertion only while $Verdict is PASS and whose skipped-assertions guard never replaces a failure. The skipped-assertions FAIL (#1448), logged after the PASS lines of the assertions that ran, still wins over them: a sticky failure, not "first line wins". A RUN line resets the fold, so a failure cannot outlive its run. The fix is in the parser, so it also covers logs written by runners generated before it. The generator is unchanged: logging one verdict per test would change a flow whose verdicts were measured on 11.13.0, and it would cover nothing more, because parser and generator ship together. Measurement (unit level, the symptom's layer: the parser reads a log). sticky_failure_test.go, run before the fix existed and again with results.go reverted: --- FAIL: TestLaterPassDoesNotClearEarlierFailure FAIL then PASS: status = PASS, want FAIL FAIL then PASS: message = "", want "Expected $r = 1, actual: 2" FAIL then PASS: the suite passed, so `mxcli test` exits 0 --- FAIL: TestFirstFailureMessageIsReported message = "Expected $r = 4, actual: 2", want "Expected $r = 1, actual: 2 (and 2 more failures in this test)" --- FAIL: TestLaterPassDoesNotClearEarlierError ERROR then PASS:test_1: got PASS "", want ERROR "Setup failed: Shop.Seed" TestSkippedAssertionsFailureStillWins and the PASS-then-FAIL and PASS-then-PASS cases pass both before and after; they are the controls. With the RUN reset stubbed out, TestRunLineStartsVerdictAfresh fails. mdl-examples/bug-tests/test-runner-1753-earlier-expect-fails.test.mdl is the runtime reproduction. It was not run against an app for this change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
…he after-startup runner Symptom, from #1753's sibling note: "on this runner, `assignPattern` matches only upper-case `CALL|CREATE`. So mdl 1's `$r = call …` is not renamed per test, and two tests that both use `$r` fail injection (MDL063 / CE0111, measured)." Affected: every Docker run and `--local --legacy-runner`. The endpoint runner gives each test its own flow and does not rename. Cause: the legacy runner compiles every test into one microflow, so it gives each test's variables a `_N` suffix. assignPattern decides which variables are renamed, and it matched only `CALL|CREATE` in upper case. MDL keywords are case-insensitive, and mdl 1 and `fmt` write them in lower case, so `$r = call microflow …` stayed `$r` in every test. Fix: assignPattern matches `call|create` in any case, followed by a word boundary so `$r = callback` does not count. No other pattern in cmd/mxcli/testrunner makes the upper-case assumption: the rest lower-case their input or use EqualFold. TestThrowsCatchesEveryCallKind and TestVerdictCatchesEveryCallKind matched the legacy output against the unrenamed lower-case statement, so they passed only because of the defect. Their legacy assertions now expect the renamed statement (legacyStmt). The 1448 bug-test's note that the legacy runner "needs a distinct variable name per test" is updated. Measurement. rename_keyword_case_test.go with the old pattern (the upper-case control subtest passes): --- FAIL: TestLegacyRunnerRenamesLowerCaseAssignments/call_microflow,_mdl_1 test 1: no "$r_1 = call microflow" — the assignment was not renamed an assertion still reads the unrenamed $r MDL063: '$r' is created twice in this microflow — first by call microflow, then by call microflow. ... mxbuild rejects this with CE0111 "Duplicate variable name" (and the same for mdl 0, call java action, create object, create list, mixed case) --- FAIL: TestAssignPatternNeedsAWholeKeyword control: $r = call microflow was not picked up: map[] I also swept every non-.fail `.test.mdl` in mdl-examples/ (16 files) through GenerateTestRunner and executor.ValidateProgram. Before the fix, 10 files drew MDL063, among them the 1448, 1514 and 923 bug-tests and doctype-tests/microflow-spec (86). After it, 2 do. Those 2 come from `retrieve $Brands` and from `retrieve $Request` / `$Request = head(…)`. They are a separate gap: assignPattern has never matched `retrieve`, `declare`, list operations, `send rest request` or `execute database query` in any case. This change does not address it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
… omits it Symptom (#1218): a bare `create or modify module role M.R;` reported "Modified module role: M.R" and erased the role's stored description. `describe` -> `exec` was unaffected because describe prints the description; grants were untouched. Cause: CreateModuleRoleStmt.Description was a plain string, so "no description clause" and `description ''` were the same value, and the OR MODIFY branch of execCreateModuleRole passed it to AddModuleRole, which overwrites the existing role in place. Fix: Description is a *string, nil when the statement has no clause, as CreateUserRoleStmt's properties have been since #707. OR MODIFY writes the stored description when it is nil, so the write is elided and the run reports "Unchanged"; `description ''` clears it. Adopting an auto-provisioned role still drops its marker description. Docs (skill, docs-site reference, `mxcli syntax`) state the rule. Measurement: - Unit test (mdl/executor/issue1218_module_role_description_test.go) fails before the fix, and with the fix forced back, with the reported symptom: description written = "", want "Read the published OData services. Nothing else." / Modified module role: Shop.ApiUser. - Real app, Mendix 11.13.0: role created with a description; bare statement with the fixed build -> "Unchanged module role", describe keeps the description, describe -> exec "Unchanged"; with the pre-fix build -> "Modified module role" and the description is empty. `description ''` with the fixed build clears it. mx check: 0 errors in every state, the same as the blank app, fault included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
…fold at 10.0 Symptom (#1163, 02-microflow-examples on 9.24.30): every count/sum/average/minimum/maximum in a microflow was refused with "this Mendix 9.24.30 project cannot store Microflows$AggregateAction.AggregateFunction (Mendix 10.0.0+)". Cause: not an older storage shape. The vendored generated version data gave aggregateFunction the row of reduceReturnDataType ({Introduced 10.0.0, Required}) and had no row for reduceReturnDataType, so the property floor refused the key every aggregate carries and let the fold's return type through unrefused. Evidence: mendixmodelsdk 4.117.0 has aggregateFunction with no version (its default even changes at 9.24.0) and reduceReturnDataType introduced 10.0.0. Studio Pro 9.24.0 stores AggregateFunction on 10 of 10 aggregates in Workflow Commons 2.6.0. A floor-off build's aggregate survives `mx convert` 9.24.30 with every key. Fix: correct the Microflows$AggregateAction row by hand (gen cannot be regenerated here) and regenerate the copy in modelsdk/version. Measured: bug-test 1163-aggregate-on-mendix9.mdl on a fresh 9.24.30 app: exec writes all five aggregates, mx check 0 errors, mx convert strips nothing. Before, the first was refused. Control: the new tests fail on the unfixed table with the reported refusal. 02-microflow-examples on 9.24 now stops at the expression aggregate (`sum … of …`, UseExpression and Expression, Mendix 10.0+), which is correct. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
Symptom (#1163, 25-rename on 9.24.30): every workflow was refused with "this Mendix 9.24.30 project cannot store Workflows$Workflow.PersistentId (Mendix 10.21.0+)". The same held on 10.0-10.20. Cause: the workflow writer, and the ALTER WORKFLOW mutator for the activities it inserts, mint a fresh PersistentId on the workflow and on every activity, outcome and boundary event. mendixmodelsdk introduces PersistentId in 10.21.0 on all four (Workflow, and the abstract WorkflowActivity, Outcome and BoundaryEvent). The floor refused the workflow's own. It never saw the others, which are declared on supertypes it does not look up. `mx convert` 9.24.30 strips the PersistentId from all 6 element types a floor-off build wrote. Fix: canon.LowerToProjectShape drops PersistentId from every Workflows$ element below 10.21. It runs on every unit write before the floor, so CREATE, CREATE OR MODIFY and wfmutator's raw inserts are all covered. The ID is dropped rather than refused because the writer mints it; no script states it, and Studio Pro 9.24 stores none. Measured: bug-test 1163-workflow-on-mendix9.mdl on a fresh 9.24.30 app: exec ok, mx check 0 errors, `mx convert -p` strips no key. 25-rename on 9.24: FAIL (the refusal) -> PASS. Controls: 25-rename and 24b-workflow-mendix10 PASS on 10.24.24 and 11.13.0. The unit test fails on the unfixed code with the reported refusal. Not fixed: a parallel split writes Workflows$EndOfParallelSplitPathActivity, a 10.21 type, and 9.24 then does not load the project. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
Symptom (found while measuring #1163): with PersistentId lowered, `mx convert -p` 9.24.30 strips nothing from an mxcli-written workflow but adds WorkflowOnStateChangeEvent: null and UsertaskOnStateChangeEvent: null. Studio Pro's next save then differs in property names, which Mendix's merge engine refuses to compare (#1373). Cause: the two defaults (mendixmodelsdk: introduced 9.12.0, removed 11.0.0) were measured on 10.24.24 only, and an entry is added only from the lowest version it was measured on. Fix: lower their measured floor to 9.24.30, where `mx convert` adds both with the same null value. Measured: bug-test 1163-workflow-on-mendix9.mdl on a fresh 9.24.30 app: mx check 0 errors, and `mx convert -p` now neither strips nor adds a key on the workflow. Before, it added these two. The test fails on the previous floor (present false on 9.24). The 11.13 case stays absent as the control. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
Symptom (#1163, 14-project-settings on 9.24.30): `alter settings constant @M.C value '...' in configuration 'Default'` was refused with "this Mendix 9.24.30 project cannot store Settings$ConstantValue.SharedOrPrivateValue (Mendix 10.9.0+)". The same held on 10.0-10.8. Cause: the settings overlay writes a new override as a Settings$SharedValue nested under SharedOrPrivateValue. From 10.9 that is the only shape Mendix reads. Below 10.9 an override is {$ID, $Type, ConstantId, Value} with a flat string (mendixmodelsdk: Value deleted and SharedOrPrivateValue introduced in 10.9.0). With the floor off, `mx convert` 9.24.30 turns the nested form into Value: "" and the override silently becomes empty. Fix: canon.LowerToProjectShape replaces SharedOrPrivateValue with the flat Value in its place, or fills the empty flat sibling the overlay leaves. A Settings$PrivateValue has no flat form, so it is refused, naming the constant. The reader already preferred a flat Value; a backend test now pins reading and updating a 9.x flat override. Measured: bug-test 1163-constant-override-on-mendix9.mdl on a fresh 9.24.30 app: describe settings shows 'broker:9092', mx check 0 errors, and mx convert keeps the value and changes no key in the settings unit. 14-project-settings on 9.24: FAIL (the refusal) -> PASS. Controls: 10.24.24 and 11.13.0 PASS. The unit tests fail on the unfixed code with the reported refusal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
Symptom (#1163, 29-datagrid DG12 on 9.24.30): a page passing `$currentObject` to a microflow or nanoflow was refused with "this Mendix 9.24.30 project cannot store Forms$MicroflowParameterMapping.Expression (Mendix 10.10.0+)". The same held on 10.0-10.9. Cause: bindParameterMappingValue writes such an argument as Expression text with Variable null. Expression is 10.10.0 on both mapping types. Before 10.10 a mapping holds only Variable, a Forms$PageVariable that is required there. Measured with the floor off: 9.24 does not load the page at all ("An error occurred when trying to set the 'Variable' property of a Microflow parameter mapping ... Value cannot be null"). Studio Pro 9 stores no mapping for an argument that is the enclosing object; it infers it. The 9.24.30 starter app and Workflow Commons 2.6.0 (9.24.0) have 82 flow calls taking 1-2 parameters and no mapping on any of them. Fix: below 10.10, canon.LowerToProjectShape leaves a $currentObject (or empty) argument out of every ParameterMappings list. An argument bound through Variable is kept; the floor drops its empty Expression. Any other expression has no form there, so it is refused, naming the parameter. Measured: bug-test 1163-flow-arguments-on-mendix9.mdl on a fresh 9.24.30 app: exec ok, mx check 0 errors, mx convert changes no key on the page, and describe -> exec is Unchanged. 29-datagrid on 9.24 now stops at DG15 (page variables, 10.17, a correct refusal); the DG2 NullReferenceException in mx check is the separate CE0463 gap. Controls: 29-datagrid PASS on 10.24.24 and 11.13.0. The unit tests fail on the unfixed code with the reported refusal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
Symptom (#1163, 10-odata and 18-folder on 9.24.30): a published OData service was refused at write time with "this Mendix 9.24.30 project cannot store ODataPublish$PublishedODataService2.IncludeMetadataByDefault (Mendix 10.8.0+)". By then the script's earlier statements were already written. Cause: not the property. mxcli writes the service as ODataPublish$PublishedODataService2, a document type mendixmodelsdk introduces in 10.4.0. Before 10.4 a published OData service is a Rest$PublishedODataService, which is deleted in 10.4.0: a different document with its own children, and mxcli does not write it. The generated version data has property floors only, so the floor tripped over the first versioned key. Measured with the floor off: mx check 9.24.30 cannot load the project ("TypeCacheUnknownTypeException: The type cache does not contain a type with qualified name ODataPublish$PublishedODataService2"). Lowering the property would have made an unloadable app. Fix: a version gate. `create [or modify] published odata service` needs integration.published_odata_service, 10.4.0. `check -p` and exec's pre-flight report it as MDL-VERSION01 before anything is written, with a hint naming the 9.x document. Writing Rest$PublishedODataService for 9.x is not attempted here. Measured: bug-test 1163-published-odata-below-10-4.mdl with `check -p` on a fresh 9.24.30 app reports MDL-VERSION01. 10-odata and 18-folder on 9.24 now stop at the gate. Controls: both PASS on 10.24.24 and 11.13.0. The new gate cases fail without the gate (0 violations). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
The 9.24 doctype run for #1163 split five refusals three ways: a wrong floor in the generated data (AggregateFunction), a document type the version lacks (PublishedODataService2, where lowering would have produced an unloadable project), and an older shape that stores nothing (a flow argument that is the enclosing object). Record what settled each one: the SDK's version data, Studio Pro-authored bytes from a Marketplace .mpk, and `mx convert -p` on the older version against a floor-off build, comparing values as well as keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
Fix test-runner false pass, module role description loss, and Mendix 9/10 property rows
`git remote get-url` applies url.<base>.insteadOf, and doctorEnv left the system, global and GIT_CONFIG_COUNT/KEY/VALUE configuration in place. In a sandbox that rewrites git@github.com: to https://github.com/ through the environment, the SSH remote the tests add already read back as HTTPS, and TestGitDoctor_SSHRemoteRewrittenByFix and TestGitDoctor_SSHURLFormRewrittenByFix failed `make test` on main (#1670) while CI was green. A ~/.gitconfig rewriting https to git@ fails the same two tests the other way. doctorEnv now sets GIT_CONFIG_GLOBAL to the null device, GIT_CONFIG_NOSYSTEM=1 and GIT_CONFIG_COUNT=0. Control: before the fix both tests fail under each rewrite separately (environment, and HOME with an https->git@ insteadOf); with an empty HOME and no environment rewrite they pass. After it, both rewrites pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
fix(test): git doctor tests no longer read the developer's git config
go.mod's toolchain line makes `go` (and `make fmt`) switch to go1.27.3, but fmt-check ran a bare `gofmt`, which is whatever Go is installed. With go1.24.7 installed, `make lint` failed main on a file CI accepts (mdl/backend/mfmutator/splice_handler_test.go) and passed a file CI then rejected (#1759). GOFMT defaults to $(go env GOROOT)/bin/gofmt. Control: `make fmt-check GOFMT=/usr/local/go/bin/gofmt` still fails on splice_handler_test.go; the default passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
`make build test lint` covers less than CI's build-and-test job gates on: check-mdl, check-findings, check-skill-index, check-wiki-pages and the rest ran only in CI unless someone remembered them. ci-local runs the job's steps in its order (check-skill-mdl standing for its three skill/docs MDL steps), without govulncheck, which the job installs @latest over the network, and without the integration jobs, which need mxbuild. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
Holds this repository's conventions for driving a PR Claude opened to green: the user reviews before the PR is opened and it is merged on green by default (merge commit, expectedHeadSha, branch restarted from main afterwards); `make ci-local` before every push; the macOS tunnel-seam job that is cancelled before it gets a runner, and so gets one re-run; the conflicts this repo keeps hitting (CHANGELOG, generated indexes and tables); and how to treat the AI PR Review comment's Critical / Moderate / Minor sections. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
`git clone` never fetches refs/notes/*, so on a fresh clone or a CI checkout `git doctor` reported every commit as "without a note" and exited 1, while the same report said `[x] origin has refs/notes/mx_metadata`. `--fix` then wrote computed notes over commits the remote already annotates, and the push it recommends was rejected as non-fast-forward. Anyone who forced that push would overwrite the real notes on the remote. The remote check already reads the remote's notes commit with ls-remote; it is now passed on. If the local ref is not, or does not contain, that commit, the commits are not judged. The finding is the unfetched ref, given with the command that fetches it. `--fix` fetches it as a fast-forward only, so a note that exists only locally is never dropped, and stops if that fetch is refused. Test: TestGitDoctor_UnfetchedNotesAreNotMissing (bare remote, pushed note, fresh clone). Before the fix it fails with the reported symptom: "[ ] 9450425668 without a note" next to "[x] origin has refs/notes/mx_metadata", and --fix replaced the remote's note on HEAD. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82tNcDLefAskVSzcKPm
…rewrite `git doctor` read the remote with `git remote get-url`, which applies url.<base>.insteadOf. With a global https -> git@ rule (a common developer setup), an HTTPS remote read as SSH and the doctor exited 1. `--fix` then set the configured URL to HTTPS on every run, which the rule rewrote straight back, and dropped the URL's `.git` suffix along the way. #1765 isolated the tests from this and left the product question open; this answers it. The check now reads `git config --get remote.<name>.url`, the value --fix actually writes. An SSH remote is still found and rewritten when a rule makes it read as HTTPS. When the effective URL differs, the report gives it as an [i] line naming the insteadOf rule, which --fix leaves alone. It is not counted as an issue: the rule applies to every git command on the machine, and changing the remote cannot undo it. Tests: TestGitDoctor_InsteadOfRewriteIsNotAnSSHRemote and TestGitDoctor_ConfiguredSSHRemoteFoundBehindRewrite. Both use a rule in the test repository's own config, on a host nobody rewrites. Before the fix: "origin: git@git.example.com:org/app.git — SSH; Studio Pro needs HTTPS", --fix changed the configured URL to https://git.example.com/org/app, and the configured SSH remote was missed. The two existing SSH tests also pass now in an environment that rewrites github.com URLs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82tNcDLefAskVSzcKPm
`mxcli git commit --dry-run` staged everything in the real index to report it, then undid that with `git reset -q`. A reset goes back to HEAD, not to the state before the run, so anything the user had staged was unstaged. The docs promise that a dry run writes nothing. A dry run now stages into a temporary copy of the index (GIT_INDEX_FILE), so the real index is never touched: staged changes, stat data and intent-to-add entries all stay as they were. A real commit still uses the real index, as before. `gitOutInEnv` is the git helper with extra environment. Test: TestGitCommit_DryRunKeepsTheUsersStaging. Before the fix: `staged after --dry-run = "", want exactly what the user had staged (staged.txt)`. The existing dry-run test started from an empty index, where the bug and the correct behaviour look the same. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82tNcDLefAskVSzcKPm
`mxcli git commit` dropped any path with a segment named `.mendix-cache`, `deployment`, `theme-cache` or `.mxcli`, at any depth. So `resources/deployment/endpoints.xml` was silently left out of the commit. The only trace was a count in "Left out 1 file(s) Studio Pro never commits". Those folders are the project's own: Studio Pro, mxbuild and mxcli create them directly beside the .mpr. isJunkPath now matches them there only. When the project sits in a subfolder of the repository, that subfolder is the anchor, taken from gitNoteRepo.MPRPath. The lock, backup and user-settings file names still match at any level. Tests: TestGitCommit_NestedFolderNamedLikeJunkIsCommitted and TestGitCommit_JunkAnchoredAtProjectInSubfolder. Before the fix both fail: "resources/deployment/endpoints.xml is not the project's own folder and must be committed", and the same for javascriptsource/theme-cache/, app/resources/deployment/ and tools/deployment/. The project's own deployment/ and theme-cache/ stay out, as the existing spaced-path test also checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82tNcDLefAskVSzcKPm
Add the steward skill and `make ci-local`; fmt-check uses the toolchain's gofmt
fix(git): doctor and commit — unfetched notes, insteadOf, dry-run staging, nested build folders
…fig hide rules A pluggable widget's enumeration that its editorConfig hides must hold its default, or mx check reports CE0463. mxcli refused that for the one Timeline rule it read (`groupEvents: false`), but `widget describe timeline` showed "2 of 6 editor hide-rules recognized". So `customVisualization: true, groupByDayOptions: 'dayMonth'` and `groupByKey: 'day', groupByMonthOptions: 'monthYear'` passed check and exec and failed mx check (#1762, mendixlabs#1668). Two shapes were unread: - terser folds the statements before a `switch` into its head, `switch(A ? hide : hide, B || hide, e.groupByKey)`, so the THEN branch's guard is bounded by the `switch(` paren, which parseGuard took for a conditional group. A `switch(`/`if(` head directly in a function body is now statement-level; a call's paren still is not. - a hide in a `case`/`default` arm had no reading. switchArmCondition now states the arm's values as eq/in, or ne/notin over the other cases for a default arm, following fall-through and grouped labels. Anything it cannot state exactly (a guarded hide inside the arm, a non-literal label, an arm whose exit is unclear, a switch under an `if`) yields no rule, as before. Timeline is 6 of 6 (3.1.1, 3.2.2, 3.2.3), so each hidden enumeration now gets MDL-WIDGET10's existing verdict: an error for a non-default value, which exec's preflight refuses before writing; a warning at the default; written at the default when left out. Across every installed package dumped before and after, only Timeline and HeatMap gained rules (HeatMap folds an `if(` head the same way). MDL-WIDGET10 names an in/notin set instead of "matches". WidgetDefGeneratorVersion goes to 20 so cached definitions regenerate. The custom-mode warning on title/description/timeIndication came from the hand-written fallback table, whose comment claimed only TextTemplates can cause CE0463. That comment, and the writer's, are corrected. File Uploader's ternary-wrapped hidePropertiesIn (#258 item 1) was already lifted by the else-if chain support of mendixlabs#1583. It is pinned by a test against File Uploader 2.5.0. Measured on Mendix 11.13.0 with Timeline 3.2.2, one widget per variant: the six hidden variants were CE0463 and the three controls 0 errors before the fix; check now refuses exactly those six. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
…default Once the Timeline's hide rules are read (#1762), one of its two format enumerations is always hidden: by customVisualization, or by the groupByKey switch. The generic pluggable-widget describe printed every stored primitive, so each Timeline came back with a hidden property at its default and drew an MDL-WIDGET10 warning on mxcli's own output (11 warnings on six measured pages). The same happened before for any widget hiding an enumeration by a rule the extractor already read, such as a Timeline with `groupEvents: false`. Such a property carries nothing: exec writes a hidden property the script leaves out at that same default. extractExplicitProperties now drops a top-level property that the widget's own rules hide in the stored configuration while it equals the stored schema's default. The rules are the registry's, the ones `check` evaluates, so the two agree about what is hidden. A hidden property holding a non-default value is still printed, because that widget fails mx check with CE0463, and printing it lets `check` say so. describe -> check -> exec on three Timeline configurations: no MDL-WIDGET10, and the stored page is byte-identical afterwards. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
… place Re-running `create or modify microflow|nanoflow` with only a filter's expression changed replaced the activity: the activity, its action and its operation all got new $IDs, while a decision whose condition changed the same way kept its own, and exec reported both as `spliced: 1 replaced`. Studio Pro compares and merges by $ID, so it showed the edit as one activity removed and another added (#1761, mendixlabs#1667). A splice Replace by one activity of the same activity and action $Type now edits the stored activity: the replacement is written into the stored slot, at the stored size, and its elements are paired structurally with the stored ones (same $Type at the same key; list entries by the member, parameter or language they name; unnamed entries by position only for equal $Type sequences). The stored $IDs go on at Bytes, rewriting every reference in the same pass, so the executor can still name the fragment's activity until then. This applies at top level, in a loop body and to an activity with a custom error handler, for create or modify and for alter ... replace. A run of adjacent statements each changed in its own place, pairwise of one plain kind, is diffed one-for-one instead of as a replace-and-drop of the whole run, so each is edited in place. The report tells the two outcomes apart: `edited in place` for a node that kept its $ID (an activity of the same kind, a decision's condition, a return's value, a node's notes) and `replaced with new element IDs` for one that did not. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
The alter syntax help, the alter-microflow reference page and the microflow and nanoflow skills now say that an activity replaced by one of the same kind is the stored activity edited, keeping its $ID and those of its action and the parts that pair up, and how exec reports it (`edited in place` against `replaced with new element IDs`) (#1761). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
…w Mendix 10.21 Symptom (#1163): on Mendix 9.x and 10.0-10.20 a workflow with a `parallel split` made the project unloadable. exec reported "Created workflow", then mx check died with "TypeCacheUnknownTypeException: The type cache does not contain a type with qualified name Workflows$EndOfParallelSplitPathActivity". Cause: the builder and the ALTER mutator end every path with that marker, because from 10.21 the engine runs a path only up to it. The type itself was introduced in 10.21.0 (mendixmodelsdk 4.117.0). The generated version data holds property floors only, so nothing compared a written $Type with the project's version. Older shape, measured: Studio Pro 9.4.0 (the Marketplace's "Example project workflow extension" 2.0.0) stores a split whose ParallelSplitOutcome is {$ID,$Type,Flow} and whose paths hold their activities and nothing more. Lowering produces exactly that key set. Fix: canon.LowerToProjectShape drops the marker from every Workflows$Flow below 10.21, for every writer at once (create, create or modify, and a path alter workflow inserts). Measured on fresh apps, the bug-test exec'd then mx check: - 9.24.30.51827: before TypeCacheUnknownTypeException, after 0 errors; mx convert 9.24.30 changes no key; describe -> exec "Unchanged". - 10.12.19.81612: before TypeCacheUnknownTypeException, after 0 errors; mx convert 10.12.19 changes no key. - 10.24.24 and 11.13.0: 4 markers, 0 errors, key sets identical to the unfixed build. Control: stubbing the lowering fails TestNormalize_ParallelSplitPathsBelowMendix10_21 on 9.24 and 10.20 with the marker still in each path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
…Mode their floors Symptom (#1163): on Mendix 9.0-9.19 every pluggable widget's schema lost the Type of its return types and kept an AssignableTo that version does not have (49 return types across the embedded templates). A return type reaching the floor there was refused with "this Mendix 9.18.0 project cannot store CustomWidgets$WidgetReturnType.Type (Mendix 9.20.0+)". Cause: the vendored generator gave a property the row of the next property in the SDK's versionInfo and left that one out, as it did AggregateFunction. mendixmodelsdk 4.117.0 has WidgetReturnType `type: {}`, `assignableTo: {introduced 9.20.0}`, and pages.GroupBox `caption: {}`, `headerMode: {introduced 8.10.0}`. widgetTypeForVersion drops undeclared keys from the widget schema by the same data, so it dropped the right key. Evidence: Studio Pro stores {EntityProperty, IsList, Type} on 208 of 208 return types in a 9.1.1 app and 326 of 326 in a 9.4.0 app (Marketplace samples), and adds AssignableTo on 57 of 57 in a 9.24.3 one. The group box row changes nothing mxcli writes: the caption is stored as CaptionTemplate, which the table never keyed, and 8.10 is below every supported version. Workflows$CallMicroflowTask.boundaryEvents was already 10.14.0, as in the SDK, and stays. Measured on 10.12.19 with that floor removed: mxcli writes BoundaryEvents [2] on every call and mx convert strips it, so the floor's drop-empty is right. A test now pins it; removing the row fails it. Fix: correct both rows by hand and regenerate modelsdk/version. Only those two rows change in the copy. Measured: the bug-test on fresh 10.24.24 and 11.13.0 apps checks at 0 errors before and after, with identical key sets. 9.24.30 stores Studio Pro 9.24.3's return type keys both times (its mx check stops on a separate pre-existing NullReferenceException in WidgetValue.IsDefault). Control: the new tests fail on the old rows (49 wrong key sets at 9.18; the Type refusal). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
…e Studio Pro's own (measured on the lab repo, S0)
…he junk just unstaged (lab finding F4)
Concord, the optional second MCP server, is no longer needed: Studio Pro's own server covers what it supplied. - DROP calls ped_delete_document (11.16+) and no longer falls back to Concord; a server without it gets an error naming the version. - --mcp-check runs a project-wide ped_check_errors (11.15+, all pages). - --mcp-run starts the app through execute_handler mendix_app (11.16+), gated on tool presence and a query_handlers getDetails lookup. - --mcp-save goes: PED persists every write to disk as it lands. - --mcp-concord / --mcp-concord-dial, ConcordConnected and concord.go are removed; the capability report names the three paths instead. - Includes the native-delete work: Client.HasTool, and CallTool classifying "Error executing tool handler: ..." (isError:false) as an error. Docs, the live-edit skill, the feature matrix and PED_MCP_CAPABILITIES.md are updated; MCP_SAVE_VERIFICATION.md (a --mcp-save procedure) is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Proposes 'mxcli playwright check --mcp <url>': the same verdict and assertions, driven through Studio Pro 11.16's browser_* handlers instead of a local Chromium. Maps each PageSignals field to its handler source, records the absent evaluate/screenshot handlers, and leaves the handler input schemas as an open measurement (query_handlers timed out). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ll-aa5aa4 # Conflicts: # .claude/skills/fix-issue/findings/mdl-backend/2026-11-01-mcp-drop-ignores-ped-delete-document.json # CHANGELOG.md # docs/01-project/MDL_FEATURE_MATRIX.md # mdl/backend/mcp/businessevent.go # mdl/backend/mcp/capabilities.go # mdl/backend/mcp/capabilities.yaml # mdl/backend/mcp/concord.go # mdl/backend/mcp/constant.go # mdl/backend/mcp/delete.go # mdl/backend/mcp/delete_test.go # mdl/backend/mcp/enumeration.go # mdl/backend/mcp/microflow.go # mdl/backend/mcp/nanoflow.go # mdl/backend/mcp/page.go # mdl/backend/mcp/workflow.go
MPR012 named only the static and dynamic image. The classic drop-down, the legacy data grid and the reference selector are CE0582 under the React client too, and lint said nothing about the ones a project already holds. The set is measured, not taken from the documentation: mxbuild 11.16.0 on a project switched to the React client reported CE0582 on the data grid and the reference selector (also with a select page) beside both image widgets, and 11.14.0 on the drop-down. The controls stay silent and are pinned by the test: navigation tree, menu bar, simple menu bar, file manager and scroll container (11.16.0), image uploader (11.13.0), and the common input and container widgets (11.15.0). Template grid and the reference set selectors were on no web page available to measure and are left out. The rule is renamed ReactUnsupportedWidget (ID unchanged), and each finding names its replacement. Closes #564 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Through the generic `pluggablewidget 'com.mendix.widget.web.datagrid.Datagrid'` form, a custom-content column with `Caption: ''` (or none) got its MDL column name as the header text, and end users saw "colTriage". Mendix stores no name for a column, and Data grid 2 renders the header text as is (`header.trim() || ' '` in Datagrid.mjs). The name was a stand-in for an empty header, which ledger #54 round 2 took to be CE0463. It is not: that CE0463 was a null TextTemplate, and the empty Forms$ClientTemplate that update-widgets writes builds clean (ChipCoV8 #11; 0 errors on 11.15.0, where the keyword `datagrid` form already writes it). Verified in the running app (11.15.0): the page written by the old build renders "colTriage" in its header, the one written by this build does not. Closes #535 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ot read At Production security the runtime checks member access on every association step of a page data source's XPath, on the entity the step starts from, and refuses the retrieve: the page opens to an error dialog, "No access rights for Shop$Account/Shop.Order_Account". mxbuild, check and lint were all clean. Measured on 11.15.0 with run --local and playwright check, as a user role holding one module role: - owner Both, traversed from the TO entity, member at None: refused; - default owner, traversed from the FROM entity, member at None: refused; - default owner, traversed from the TO entity (not a member there): opens; - the same XPath inside an entity access rule: opens; - control, members granted ReadOnly: every page opens. SEC011 reports exactly the refused cases, per user role that can open the page and reads the starting entity, as an error naming the member to grant. Live, it flags the two refused pages of the measured app and nothing on the control; on FactoryManagement (Production, 27 page XPaths with paths) it reports none. Closes #945 Closes #1337 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ll-aa5aa4 # Conflicts: # cmd/mxcli/engine.go # cmd/mxcli/mcp.go
…rted-widgets # Conflicts: # CHANGELOG.md
…caption # Conflicts: # CHANGELOG.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… command line Merges the pull request's head (2c118da) as it stands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82tNcDLefAskVSzcKPm
gitInE and the real-mx integration fixture ran git without the identity gitIn sets. git merge needs one even when it stops on a conflict, so on a host whose name gives git nothing to guess from (a bare "vm", no domain) every git-merge fixture failed before the command under test ran. The integration fixture drops the global config, so the command's own merge and commit there need the identity in the process environment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82tNcDLefAskVSzcKPm
When git merge stopped before any conflict existed, the error was built from its stdout, which holds at most an "Updating a..b" line; git's reason is on stderr. A dirty working tree came out as "git merge side: Updating 5d6b73c..2c3fd47" and a missing identity as "git merge side: " with nothing after it. Both the merge and the --plan path now report git's own message. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82tNcDLefAskVSzcKPm
When Mendix's merge engine stops on a decision it still writes a result and marks each undecided unit in its Unit row: ContentsConflicts holds a JSON list instead of '' (every other row holds '', never NULL; measured on mx 11.13), TreeConflict is non-zero for a conflict about whether the document exists. mx check reports CE0137 while a marker is present. Reader.UnitConflicts lists the marked units. Writer.ClearContentsConflicts records contents conflicts as decided, leaving the row as the engine writes every unit it merged, under the Studio Pro guard and with _Transaction bumped like every other write; tree conflicts are left alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82tNcDLefAskVSzcKPm
applyPlan resolved from git's two sides: ours for every model file git conflicted, theirs for the planned units. With the template listing only the engine's ManualResolutionRequired units, a unit the engine merges by itself was taken from ours wholesale. Measured with mx 11.13: each side adds an entity to one domain model (AutoResolved) and both edit one entity's documentation (ManualResolutionRequired); the plan names only the second unit, and its commit lost theirs' entity, hash gate passing. The plan step now runs the engine again over clean worktrees and resolves over its result: everything it merged stays merged, each planned unit is taken whole from its side's commit, and its conflict marker is cleared. The plan must name exactly the units the engine left open (a stale or hand-widened plan is refused), and nothing is committed while the index still marks a unit as conflicted. The template and the check read the same set (engineDecisions), with the result's markers as the fallback when the engine prints no -v list. The real-mx integration test applies a generated plan to that scenario on a scaffolded project and asserts both entities, theirs' page, no marker, and no CE0137 from mx check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82tNcDLefAskVSzcKPm
Its engine section (folder arguments, MINE as the working tree) is what failed on Linux; the shipped design takes three .mpr paths over clean worktrees, and --plan now resolves over the engine's result. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82tNcDLefAskVSzcKPm
MPR012: report every widget measured as CE0582 under the React client
Write an empty header for an uncaptioned custom-content data grid column
…ve-driscoll-aa5aa4 # Conflicts: # CHANGELOG.md
…xpath-association-access # Conflicts: # CHANGELOG.md
SEC011: page XPath through an association the page's roles cannot read
feat(mcp): drop Concord; check and run through Studio Pro's own MCP
feat(git): mxcli git merge (mendixlabs#1674), with --plan resolved over the engine's result
docs(proposals): playwright check through Studio Pro's MCP browser
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Syncs
ako/mxcli:maininto upstream. It carries 24 new commits from fork PRs ako#1870, ako#1871, ako#1873, ako#1874, ako#1876 and ako#1882.Fixes #1471. Merges #1674.
About the commit count: the PR shows 61 commits. The other 37 are copies, under new SHAs, of fork commits already merged upstream. They come in through the history of #1674's branch, which ako#1882 merges unchanged, so that GitHub sees #1674 as merged. They change nothing beyond what is already on
main.mxcli git merge(#1674, by @engalar)ako#1882 merges #1674 at its head
2c118daf, unchanged, with engalar's commits keeping their author and committer. It then fixes what review and a Linux run found, one commit per fix.What #1674 brings:
mxcli git merge <ref>runsgit merge. On a model conflict it runsmx merge -vover clean worktrees of base, ours and theirs.mx diff;--ours/--theirs, or Studio Pro.git commitandgit doctor:doctorjudges onlycore.autocrlf, because themendix.*counters are Studio Pro's own.Fixes on top:
--planresolves over the engine's result. The plan's commit used to be built from git's two sides, so it lost changes the engine had auto-resolved. For example, theirs' new entity was dropped when both sides added an entity to one domain model.--plannow runs the engine again and works from its result.Reader.UnitConflictslists the units marked inUnit.ContentsConflicts, andWriter.ClearContentsConflictsclears one. A marker left behind gives CE0137 in mx check.MCP backend: Concord is dropped
Studio Pro 11.16's own MCP server now covers what Concord, the optional second MCP server, supplied:
delete_document, the DROP fallbackped_delete_documentonly (11.16+)--mcp-save--mcp-checkped_check_errors(11.15+)--mcp-runexecute_handlermendix_app(11.16+)--mcp-concord,--mcp-concord-dialand--mcp-save.mxcli mcp capabilitiesreports the DROP,--mcp-checkand--mcp-runpaths, each with the version it needs.runitself, because it would start the maintainer's app.playwright checkthrough Studio Pro's MCP browser.Lint
where [Shop.Order_Account/Shop.Order/IsActive = true()]fails at Production security withSecurityRuntimeException: No access rights, while mxbuild, check, exec and lint were all clean.run --localandplaywright check. The same XPath inside an entity access rule opens fine.Pages
column colTriage (Caption: '')rendered "colTriage" in the header. An empty header builds clean and renders blank, which was checked at runtime on 11.15