Repository navigation
Conversation
ako#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 (mendixlabs#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 ako#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 (ako#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 ako#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 (ako#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 (ako#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 ako#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 (mendixlabs#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 (ako#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 (ako#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 (ako#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 ako#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 (mendixlabs#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 (ako#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. ako#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 (ako#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 (ako#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 (ako#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 (ako#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`) (ako#1761). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
…w Mendix 10.21 Symptom (ako#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 (ako#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)
…iff) and a plan file the human edits before landing (lab S5c)
Sync ako/mxcli: git doctor/commit follow-ups to mendixlabs#1670, Mendix 9/10 writer gaps, module role description, test-runner false pass
Fix Timeline hidden enumerations, in-place activity edits, and the parallel split below Mendix 10.21
The pattern `.mxcli/unit-types.idx` has a slash in the middle, so git anchors it to the repository root: the index mxcli writes next to a fixture a test opens in place (testdata/testapp-views/.mxcli/) showed up as untracked, though the comment above the line says it is meant to be ignored there. `**/` matches it at any depth: `git check-ignore` did not match the nested copy before, and matches it and the root one after. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
Without @position a custom error handler is a row 130px under its activity, and the main line carries on one spacing after the activity however long the row is. When the next activity had a handler too, its row started at its own x, inside the first: the first handler's end event and the second handler's first activity were both stored at 680;330 (ako#1768, mendixlabs#1672). The lane model from mendixlabs#1154 already made an activity with a handler wait for a reserved lane; the handler row was the occupant that never reserved it. addErrorHandlerFlow now reserves the lane to the row's measured right edge plus the activity gap, and measureStatements does the same and counts the row in its extent, so a branch or loop holding handler rows is measured as built. Without the measurer half, 12 of the new shapes put a merge on an activity. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
The start event goes one spacing left of the first statement when that statement carries @position. The pre-scan took the first statement that had one, wherever it was, and every unplaced statement before it follows the start: `declare $A …; @position(840, 200) declare $B …` stored both at 840;200 (ako#1768 case 2, mendixlabs#1672). Only the first statement moves the start now, as alter's patch (patchDiff.startEvent, declared[0]) and the syntax help already assumed. The help and the language reference now say that statements before a placed one keep their derived places. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
The overlap rule built its canvas from activities, loops, decisions and merges only, so an activity lying on an error handler's end event was never reported — the shape ako#1768 reported, two handler rows on top of each other, passed MPR008 until an activity happened to land on an activity. Start, end, error, break and continue events and inheritance splits are on the canvas too, each judged with its stored size; an event that stores none is taken as Studio Pro's 20x20 rather than as an activity. On the 292 Studio Pro-authored flows of PedApp and ako/TestApp the wider set adds no finding. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
Symptom (ako#1769, mendixlabs#1673, Mendix 11.12.4): `alter microflow … { replace loop … with begin … end; }` wrote `$X = find $List by Code = …` in the loop's body as Microflows$FindByExpression with the bare text `Code = …` when the fragment did not know $List's entity. check --references and exec reported nothing; mx check failed CE0117. The same find inserted on the main line was refused (ako#891). Cause: confirmed. mendixlabs#891's qualifiedMembersOnly is set on the fragment's builder, but the loop body, the while body and the error-handler body are each built by a fresh flowBuilder whose fields the site listed by hand, and none listed the flag. So every member-bearing statement nested in a fragment (find, filter, sort, aggregate, change, at any depth) was written bare. `replace body of loop` went through the same loop builder and had the same hole. Fix: one construction helper, flowBuilder.nestedBuilder, for all three sites; it inherits the flag with the rest of the shared state. The error handler still sets its return type and label table itself, as before. TestNestedBuilder_InheritsSharedState classifies every flowBuilder field as inherited or the body's own and checks the helper against it by reflection, so the next field cannot be left out silently. With this alone the reported alter is refused, with nothing written, because the fragment does not type a list retrieved over an association; the next commit types it. Control: with the flag left out of nestedBuilder, the nested-guard test reports every statement in every nested shape written instead of refused, the PedApp test reports exec writing the replaced loop and replace body, and the structural test names the missing field. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
Symptom (ako#1769, mendixlabs#1673): after the previous commit, the reported alter — a replaced loop finding by member in $Lines, retrieved over an association before the loop — is refused with "cannot qualify member \"Code\"" where `create microflow` writes Microflows$Find on MyModule.OrderLine.Code. Cause: alterFlowContext.storedVariables, the variables a fragment starts from, typed parameters, declares, creates, database retrieves and calls, and left a retrieve over an association "Unknown": which end it yields depends on the end it starts from. A stored loop's iterator was not typed either, so a member of it in a fragment inserted in the loop body was refused too. Fix: storedVariables types both from the variable they start from, to a fixed point, so the order the stored flow lists them in does not matter. The retrieve's type is flowBuilder.associationRetrieveType: lookupAssociation, entityIsSubtypeOf and associationRetrieveYieldsList, the answer the full build and check --references already take. The parent is the FROM end and the child the TO end, so from the TO end (Order) a reference yields the list of FROM objects (OrderLine), from the FROM end the one TO object; a reference set is a list either way, an owner-Both reference one object either way. A self-reference, an unknown association, or a name two loops or retrieves give different origins stays untyped, and its member is refused rather than guessed. Controls: with storedVariables reverted, the association-typing tests and the PedApp by-attribute tests fail with the refusal; with the ambiguity guard off, the shared iterator name and the retrieve from it are typed. Measured on Mendix 11.13.0 (scratch app, real mx): before, FindByExpression "Code = $Lookup/Code" and CE0117; the previous commit alone, refused with exit 1 and the stored flow byte-identical; with both, Microflows$Find on MyModule.OrderLine.Code, mx check 0 errors, describe → exec "Unchanged microflow". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
…11.2 Symptom (ako#1771): on Mendix 10.12 to 11.1 every user task mxcli wrote failed mx check with CE1859 "XPath constraint is required". That included tasks targeted by a microflow, and multi-user tasks. exec and mxcli check were clean. `mx convert -p` 10.24.24 then stored an empty XPathBasedUserSource in place of each targeting, so the targeting was silently lost. Cause: userTaskToGen writes UserTargeting, which Mendix introduced in 11.2.0 (mendixmodelsdk: UserTaskActivity.userSource deleted and userTargeting introduced in 11.2.0). It is declared on the abstract UserTaskActivity, and the property floor looks up the concrete type only, so the key went into every version. 10.24 ignores it and fills the required UserSource with its default. Older shape, measured: two Studio Pro-authored Marketplace samples, Employee Onboarding (9.1.1, XPath) and Multilevel Dynamic Workflow (9.24.3, microflow), converted 9.24.30 -> 10.24.24 -> 11.13.0. The targeting keeps its $ID and value byte for byte. Only its $Type and the key holding it change: XPathBasedUserSource <-> XPathUserTargeting, MicroflowBasedUserSource <-> MicroflowUserTargeting, and EmptyUserSource -> NoUserTargeting (this fix's 10.24 output converted up). Fix: canon.LowerToProjectShape renames the targeting to the user source below 11.2, in its place, for every writer. Group targeting is 11.2 and has no older form, so it is refused, naming the task. Measured on fresh apps, exec of the bug-test then mx check: - 10.24.24.119653: before CE1859 x3, after 0 errors. mx convert 10.24.24 changes no key and no value. describe -> exec "Unchanged workflow". - 11.13.0: 0 errors, and the key sets match the unfixed build. Control: stubbing the lowering fails TestNormalize_UserTaskTargetingBelowMendix11_2 24 times, with UserTargeting still on the task. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
Symptom (ako#1771): on Mendix 9.x a workflow with a user task made the project unloadable. exec reported "Created workflow", then mx check 9.24.30 died with "TypeCacheUnknownTypeException: The type cache does not contain a type with qualified name Workflows$SingleUserTaskActivity". Cause: mxcli writes Workflows$SingleUserTaskActivity and MultiUserTaskActivity. Mendix introduced both types in 10.12.0 and deleted Workflows$UserTask there (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.24.3 (the Marketplace's Multilevel Dynamic Workflow) stores {$ID, $Type, Annotation, AutoAssignSingleTargetUser, Caption, DueDate, Name, OnCreatedEvent, Outcomes, TaskDescription, TaskName, TaskPage, UserSource, UserTaskCompletion}. The completion (9.22) says which kind of task it is. A single-user task has a bare SingleInputCompletion. A multi-user task has a MultiInputCompletion holding its CompletionCriteria, its TargetUserInput and, from 10.2, AwaitAllUsers. Fix: canon.LowerToProjectShape rewrites the task as that UserTask below 10.12. The multi-user settings move into the completion, and the outcomes stay on the task, so the rule's outcome pointers still resolve. The completion's $ID is derived from the task's. What the older type cannot hold is refused, naming the task: a multi-user task below 9.22, majority and threshold below 10.0, a microflow rule below 10.3, `await all users` below 10.2, and a boundary event on a user task below 10.14. An empty BoundaryEvents list is dropped below 10.14; it is declared on the abstract UserTaskActivity, which the floor never saw. describe reads a 9.x multi-user task's completion, and the create-or-modify guard now sees its rule and participants too, so a rewrite no longer resets them silently. Measured on fresh apps, exec of the bug-test then mx check: - 9.24.30.51827: before TypeCacheUnknownTypeException, after 0 errors. mx convert 9.24.30 changes no key and no value. describe -> exec "Unchanged workflow". After mx convert 10.24.24, the user tasks are identical in keys and values to mxcli's direct 10.24 output. - 10.24.24 and 11.13.0: 0 errors. Control: stubbing lowerUserTaskType fails the three normalize tests on 9.24 with the 10.12 types in place. The read test and the guard test failed before their fixes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
Symptom (ako#1771): on Mendix 9.x to 11.1, `alter workflow M.W { set (Targeting: xpath [...]) on Task; }`, and the deprecated `set activity 'Task' targeting ...` form, reported "Altered workflow" and changed nothing. describe still showed the old targeting, and mx check stayed clean. Cause: SetActivityProperty set UserTargeting with bsonnav.DSet, which only replaces a key that already exists. Below 11.2 a stored user task holds its targeting as UserSource, so no key matched and nothing changed. Fix: setUserTargeting replaces UserTargeting or UserSource in place, or adds the targeting when the task has neither. It writes the current shape; canon.LowerToProjectShape stores it as the project's version does, as it does for every other writer. Measured on fresh apps, the bug-test exec'd then described and checked: - 9.24.30.51827 and 10.24.24.119653: with the committed mutator (a scratch build), "Altered workflow" x2 and `[Name = 'admin']` kept. With the fix, `[Name = 'reviewer']` and ACT_GetReviewers, 0 errors, and the version's key set (Workflows$UserTask with UserSource on 9.24). - 11.13.0: the new targeting both ways, 0 errors. Control: TestWorkflowMutator_SetTargetingReplacesAStoredUserSource and ...AddsAMissingTargeting failed before the fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
Symptom (ako#1771): `mxcli check -p` passed scripts with user-task features the project's version cannot store, and exec wrote them. Group targeting on 10.24 failed mx check with CE1859, and mx convert emptied it. Once the user task is lowered to its pre-10.12 shape, a multi-user task's later rules were refused only at write time, part-way through a script. Cause: no version gate looked inside a workflow user task. The generated version data floors properties, never types, and these are type-level introductions (mendixmodelsdk 4.117.0 StructureVersionInfo): the multi-user task's UserTaskCompletion 9.22, Majority and ThresholdCompletionCriteria 10.0, MultiInputCompletion.awaitAllUsers 10.2, MicroflowCompletionCriteria 10.3, and XPath/MicroflowGroupTargeting 11.2. Fix: userTaskGates walks the activities a create workflow states, and the ones alter workflow inserts. It adds an MDL-VERSION01 gate for each clause the version lacks. A consensus or veto multi-user task with user targeting passes from 9.22. The floors are new sdk/versions entries: workflows.multi_user_task, multi_user_task_majority_threshold, multi_user_task_await_all, multi_user_task_microflow_rule and group_targeting. Measured with the bug-test (check -p, then exec, then mx check): - 9.24.30.51827: WF_Majority, WF_Await, WF_Microflow and WF_Groups reported; exec "Refusing to execute: 4 error(s) above. Nothing was written." - 10.24.24.119653: WF_Groups only. - 11.13.0: none; exec writes all five, mx check 0 errors. Control: TestCheckVersionGates_UserTaskFeatures failed for every case before the gates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
24-workflow-examples.mdl, the only doctype script with user tasks, is gated 11.0+ as a whole. CI's 10.24 leg therefore never wrote a user task. That hid ako#1771: every user task mxcli wrote on 10.12 to 11.1 failed mx check with CE1859. 24b-workflow-mendix10-examples.mdl (10.0+) now writes user tasks by XPath, by a microflow and with no targeting. It also writes multi-user tasks with the consensus, veto, majority and threshold rules and participants, which 10.0 has. Then it inserts a task with alter workflow and changes a task's targeting. Measured with TestMxCheck_DoctypeScripts/24b: - 10.24.24.119653: PASS, mx check 0 errors. Control: with the targeting lowering stubbed, it FAILS with CE1859 on all 8 user tasks. - 11.13.0: PASS (24, 24b and 25 all pass, on both versions). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
The 10.0-10.11 claims rested on mendixmodelsdk's version data and on the 9.24 measurements. They are now measured on 10.11.0.37074, the last version before the 10.12 type boundary (mxbuild downloaded for this, then deleted): - With the user-task lowering stubbed, a user task makes the project unloadable (TypeCacheUnknownTypeException ... Workflows$SingleUserTaskActivity). - With the fix, the 1771 bug-tests give 0 errors. The multi-user tasks' MultiInputCompletion holds AwaitAllUsers (10.2+). mx convert 10.11 changes nothing, and describe -> exec reports "Unchanged workflow". Majority, await all users and a microflow rule check clean, and alter workflow's targeting edit lands. 1771-user-task-features-by-version.mdl's group-targeted task used a microflow returning System.WorkflowGroup, an entity Mendix introduced in 11.2. Written on 10.11 it was CE1613, unrelated to the user task. The task now targets groups by XPath. Re-measured: check -p reports the expected gates on 9.24.30, 10.11.0 and 10.24.24. On 11.13.0 exec writes all five workflows with 0 errors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
Mendix 11.16.0 is published on the CDN (mxbuild-11.16.0.tar.gz, 27 Oct 2026) but was not exercised by CI. Added as a seventh entry rather than replacing an older one: 11.13.0 is the measured floor for timer-started workflow event sub-processes, and 11.15.0 is the first version with the MessageDefinition2 document shape (ako#987), so each keeps a job running at its own gate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oK82teqHJLFTo5pLuHMt1
Fix finds in nested fragment bodies, user tasks below Mendix 11.2, and overlapping error-handler rows
|
My verdict on #1674 is request changes. Merging from the command line with Mendix’s own merge engine, plus a review step, is a good idea. As it stands, though: --ours/--theirs silently drop documents. How I checked it: the permission policy blocked building and running the PR’s own code, because it comes from an external contributor. So I replayed its git steps with my own commands, and called Mendix’s mx (11.13, on the pedapp fixture) exactly as the PR calls it. Its mx git-merge success path on Windows or macOS is therefore not something I could verify. Must fix The common fix for points 1 and 2: never take a whole side of App.mpr. Either merge its list of units three ways, or refuse and abort whenever the hash check finds orphaned or missing units. |
ci: add Mendix 11.16.0 to the nightly test matrix
…he merge said — CI's modelConflictRepo saw no unresolved files on ubuntu, and the fixture threw the merge's output away
…jDir-scoped junk paths; keep the model-aware doctor wording and the git-merge entry
…ates every resolution on hashes, and aborts cleanly on every failure ako's review of mendixlabs#1674: --ours/--theirs took a whole side including the .mpr, so the other side's added units became files no longer in the project and deleted ones stayed listed with no file; --plan refused exactly that case and its error paths left MERGE_HEAD behind (Studio Pro re-runs the half-done merge on every open); every non-zero engine exit was read as a merge decision, so a crash (exit 129) wrote a plan and pointed at side-taking; and mx git-merge's file form does not run on Linux at all (its git lookup is Windows-bound). - the engine is called as mx merge BASE MINE THEIRS over the three project folders (Linux devcontainers included) through docker.PrepareMxCommand, and it owns the .mpr: its result carries both sides' document sets, so a unit one side added while the other changed existing ones is kept - exit 2 alone is a decision (report + plan template + clean abort); any other exit, and an mx that will not start, is reported as the engine failing and aborted cleanly - --ours/--theirs stay but their commit is hash-gated: when the sides' document sets differ the commit is refused (orphan/missing units) and the merge aborts — a whole side never silently drops documents - every --plan refusal aborts the merge (stale already did; invalid take, unreadable snapshots and the hash refusal now do too) - a merge already in progress is resumed instead of re-run, so the mixed-conflict advice (resolve text files, git add, re-run) works - <mpr>.merge-plan.json is junk for mxcli git commit
bd7ce18 to
4fa453f
Compare
|
Every Must fix verified against the code and addressed on the branch (fa86038cf). Outcome per point: 1. 2. Half-done merges — every refusal now aborts. 3. Exit codes — only 2 is a decision now. Exit 0 stages, hash-checks and commits; exit 2 prints the property-level report plus the plan template and aborts; any other exit (129, a crash) is reported as 4. Linux — the engine is now called as Should fix: upstream/main is merged in — CHANGELOG, the Still open, said plainly: real mx could not be run from this Windows machine, so the Linux |
…774 type floors; keep the model-aware doctor judgment and the git-merge entry
|
Correction to my earlier review: I wrote that Second blocker: with MINE set to the working tree in the middle of The engine's output, since you couldn't check it from Windows:
[{"UnitId":"0131b342-…","UnitName":null,"ModuleName":"FeedbackModule","ContainmentName":"DomainModel","Type":"MergeContents","TreeConflict":"None","ConflictResolutionDetails":"ManualResolutionRequired"},
{"UnitId":"6a912436-…","UnitName":"ACT_Theirs","ModuleName":"MyFirstModule","ContainmentName":"Documents","Type":"AddFromHead","TreeConflict":"None","ConflictResolutionDetails":"AutoResolved"}]Only Smaller points:
On the real-mx integration test: this blocker came from exactly the gap that test would cover, so I'd add it in this PR rather than a follow-up. One fixture merged through Generated by Claude Code |
Follow-up to #1670 (closes the merge half of #1669).
What happened
#1670 was merged from an earlier state of its branch: the four commits pushed after
6a5c942fanever landed on main — themendix.commits-since-gcdoctor fix, the staged-files summary fix (F4), and the entiremxcli git mergefeature. This PR re-carries exactly those four commits, rebased onto current main (all cherry-picks clean, tests green).What
mxcli git merge <ref>doesgit mergecannot merge Mendix models: the.mpris SQLite and every.mxunitis BSON, so two sides changing the model always meet a binaryCONFLICT (content)(measured in a dedicated lab repo with Studio Pro 11.12.6: both files, every time). Studio Pro resolves that with a model-aware merge, and the same engine ships as a command —mx git-merge BASE MINE THEIRS BASE_COMMIT MINE_COMMIT THEIRS_COMMIT.mxcli git merge <ref> -p app.mprrunsgit mergefirst; a merge git resolves itself (disjoint files, fast-forward) is annotated likemxcli git commitdoes. On a model conflict the base and theirs revisions are checked out into temporary worktrees andmx git-mergedecides (mx resolved for the project's Mendix version — PATH, mxcli cache, Studio Pro installation):AutoResolved, both sides kept, note identical in shape to Studio Pro's own merge commit's.ManualResolutionRequired→ a review step, never a silent choice. The merge aborts (working tree exactly as before), and two things are produced for the human:mx diff, both sides:New type > Length: 200 → 180(ours) /→ 250(theirs), per conflicted unit;<mpr>.merge-plan.json, onetake: ours|theirsentry per conflicted unit,oursby default).The human edits the plan and re-runs with
--plan <file>: each unit is taken from its chosen side, the.mprindex is repaired from the mixed files (files are the truth), and the merge is committed with the note. Plan validation refuses units that are not modified-on-both-sides (added/deleted/moved conflicts) — those, and any resolution that must mix both sides inside one unit, stay in Studio Pro (Version Control › Merge Changes Here).--ours/--theirstake one side wholesale without consulting mx. Non-model conflicts (text sources) are left like plain git.Also re-landed (were in the branch but missing from main):
git doctorjudges onlycore.autocrlf; themendix.*keys are backfilled when missing and their values are never judged — measured on a real Studio Pro checkout,mendix.commits-since-gccounts up (was2, not0) and a healthy checkout must pass (fix 1);git commit's staged-files summary lists what the commit holds, not the junk just unstaged (fix 2).Tests
TDD, real git repositories + real SQLite
.mprfixtures (cmd/mxcli/gitmerge_test.go,gitdoctor_test.go): clean merges annotated; model conflicts auto-resolved via an injected mx runner (worktree paths and base/ours/theirs shas asserted); manual conflicts reported and aborted (MERGE_HEAD gone, tree restored); plans applied per unit with hashes repaired; stale plans refused; non-model conflicts left alone;--ours/--theirsdeterministic without mx; already-up-to-date. All green locally with the real mx on the lab repository (auto-resolve, report→plan→land,--theirs).Docs
CHANGELOG (
git mergeentry under Unreleased), docs-site "Working Outside Studio Pro" › Merging branches, and theteamserver-gitskill's model-merge rule.