diff --git a/contrib/inbox/2026-10-21-macos-mendix10-mxbuild-docker-shim.md b/contrib/inbox/2026-10-21-macos-mendix10-mxbuild-docker-shim.md new file mode 100644 index 00000000..826bea71 --- /dev/null +++ b/contrib/inbox/2026-10-21-macos-mendix10-mxbuild-docker-shim.md @@ -0,0 +1,58 @@ +# macOS + Mendix 10: no native mxbuild, so the gate needs a Docker shim, and doctor.sh reads a shim that exits 3 on `--version` as broken + +**From:** a field project (anonymised) — a Mendix 10.24.18 app changed with mxcli v0.27.0 and this toolkit, on an Apple Silicon Mac +**Date:** 2026-10-21 +**Kind:** learning +**Field evidence:** `exec.sh`'s mxbuild gate and the microflow tests both ran on this Mac only +through a Docker shim; each fact below was re-checked on 2026-10-21. +Toolkit master 393d253. Related: MendixMau/mxcli-project-toolkit#525. +**Proposed target:** a "Mendix 10 on macOS" row in the lane table of `skills/doctor-triage.md` +(line 100 says the CDN toolchain is Linux-only), and the probe note in `bin/doctor.sh` + +--- + +## What is true on this Mac + +- The Studio Pro 10.24.18 app bundle has no `mx` or `mxbuild` (its `Contents/modeler` holds 502 + files, none of them named `mx` or `mxbuild`). +- The cached 10.24.18 build in `~/.mxcli/mxbuild/` is a Linux ELF (`file`: ELF, ARM aarch64). `mxcli test --local` (v0.27.0) stops with "mxbuild from the Mendix CDN + is a Linux binary and cannot run natively on darwin". +- Mendix 11 differs here. The Studio Pro 11.12.4 bundle holds `mx` and `mxbuild` as Mach-O arm64 + (`file`), and #525 reports a Mach-O mxbuild in the 11.14.0 cache. So doctor-triage's "Linux only" + is right for Mendix 10 on a Mac and wrong for the 11 builds #525 looked at. Not run: the toolkit's gate on Mendix 11 here. + +## The shim + +`find_mxbuild` honours `MXBUILD_PATH` (`project-bin/_common.sh:557`), so a script there replaces +mxbuild. The toolkit calls it as `mxbuild --java-home=… --java-exe-path=… --write-errors= +--target=deploy ` (`_common.sh:909-910`) and reads the file's `problems[]` (severity `Error`) and +`errors[]` (why nothing was checked). Name the file for the model version: `_common.sh:1044-1047` +warns when the path doesn't contain it. + +A working one is about 100 lines of bash: + +1. Take `--write-errors=` and the one argument not starting with `--` (the .mpr); ignore the rest. +2. Read the version from the .mpr: `select _ProductVersion from _MetaData`. Require the matching + Linux `mx` in the mxcli cache; otherwise stop (a different version gives a false green). +3. Start a throwaway container with that `mx` and the project mounted read-only, copy the project + in, run `mx check -j check.json`. +4. Convert `check.json` to `{"errors":[],"problems":[{"severity":"Error","errorCode","message", + "locations"}]}`, and compare its count with the line `The app contains: N errors`. +5. Exit 0 clean, 1 errors, 3 not run. Every failure to check (Docker down, wrong version, timeout, + count mismatch) exits 3 and writes `{"errors":[{"message":"…"}],"problems":[]}`: the gate reads + non-zero exit with no problems as "not verified" (`_common.sh:949`). + +It does no deploy build (no Java compile, no theme bundling). On a 10.24.18 test app it printed +`The app contains: 0 errors.`, exit 0, in 52 s. A sibling script runs `mxcli test --local +--skip-app-startup` in a container with the same 0/1/3 exits; in the field it ran 110 tests, all +passed, exit 0 (hsqldb instead of PostgreSQL, the after-startup microflow not run). + +## The doctor probe + +`probe_runs` (`bin/doctor.sh:145-166`) runs ` --version`, then `--help`; exit 126/127 means +a wrong-platform binary, and any other non-zero exit with both is "cannot run". `doctor.sh:892-899` +then reports "mxbuild exists but cannot run (exit 3)" and sets `GATE_OK=0`. A shim that wants an +.mpr and exits 3 without one fails this. Checked by running the extracted function on two stubs: +one without special handling returned 3; one answering `--version`/`--help` with exit 0 and a line of +text returned 0 (`how=--version`). So a shim should answer both with exit 0 before it looks for an +.mpr or starts Docker. diff --git a/contrib/inbox/2026-10-21-mxpatch-three-lookups-that-miss.md b/contrib/inbox/2026-10-21-mxpatch-three-lookups-that-miss.md new file mode 100644 index 00000000..55220789 --- /dev/null +++ b/contrib/inbox/2026-10-21-mxpatch-three-lookups-that-miss.md @@ -0,0 +1,80 @@ +# mxpatch: three lookups that find nothing on a domain model or a flow's own documentation + +**From:** a field project (anonymised) — a Mendix 10.24 app changed with mxcli v0.27.0 and this toolkit +**Date:** 2026-10-21 +**Kind:** learning +**Field evidence:** hit while writing a byte patch that rewords documentation (a change MDL can't +express without rewriting the element). Each lookup below was re-run on the toolkit's own fixture +model (`tests/wave2/fixtures/mxpatch/`, rebuilt from `TestApp.mpr.sql` with `executescript`, +Mendix 11.14.0) with `project-bin/mxpatch.py` on master 393d253, so nothing here depends on the +field app. +**Proposed target:** the LIBRARY section of the `project-bin/mxpatch.py` docstring (a short +"lookups that miss" list), or `skills/learned-mdl-preflight.md` next to the line that says some +changes are "not MDL at all" + +--- + +A patch author reaches for `find_unit` and `find_element` and gets `NotFound` three times. None of +the three is a bug; each is how the model is stored. + +## 1. An entity's `$Type` is `DomainModels$EntityImpl` + +The natural call, with the type spelled `DomainModels$Entity`, fails: + +``` +find_element(dm.doc, "Car", "DomainModels$Entity") + -> NotFound: no element named Car (DomainModels$Entity) in this unit +find_element(dm.doc, "Car", "DomainModels$EntityImpl") # works +``` + +On the fixture model, `find_elements(dm.doc, type_="DomainModels$EntityImpl")` returns the three +entities (`Car`, `VCar`, `ApprovalContext`), and nothing else is stored under `DomainModels$Entity`. +Leave the type off (`find_element(dm.doc, "Car")`) if the name is unique in the unit. + +## 2. A domain-model unit has no `Name` + +The unit's `Name` key is empty (`None`), so the unit is identified by its module, not by a name: + +``` +m.find_unit("DomainModels$DomainModel", "MyFirstModule") + -> NotFound: no unit DomainModels$DomainModel MyFirstModule in +mxpatch.py find --type 'DomainModels$DomainModel' + -> DomainModels$DomainModel MyFirstModule.None mprcontents/84/77/.mxunit +``` + +The `.None` in the `find` output is the missing name. The CLI's `get` and `set` require `--name` +(`mxpatch: --type and --name are required`), so they can't select a domain model at all. Use the +library and pick the unit by module: + +``` +dms = [u for u in m.units("DomainModels$DomainModel") if m.module_of(u) == "MyFirstModule"] +car = mxpatch.find_element(dms[0].doc, "Car", "DomainModels$EntityImpl") +mxpatch.set_value(car, "Documentation", "A car.") +m.write_unit(dms[0], apply=False) # dry run: prints the new byte count and ContentsHash +``` + +That sequence ran as written on the fixture model: `changed`, then a dry run for the one unit. + +## 3. A flow's own documentation is on the unit root, and `find_element` skips the root + +`find_elements` and `find_element` search "below the unit root" (`if d is doc: continue`, +`project-bin/mxpatch.py:181`). A microflow's `Documentation` is a key on the unit root, +so looking up the microflow by its own name finds nothing: + +``` +find_element(u.doc, "MyFirstLogic") -> NotFound: no element named MyFirstLogic in this unit +get(u.doc, "Documentation") -> '' # the key is on the root +``` + +Set it on the root. The CLI does this when `--element` is left out: + +``` +mxpatch.py set --type 'Microflows$Microflow' --name MyFirstLogic --key Documentation --value 'Does X.' + -> Documentation: '' -> 'Does X.' (dry run; add --apply to write) +``` + +## Not checked + +- Only a dry run was done; the patches were not applied and opened in Studio Pro. +- Only the microflow case was run for gotcha 3. Other unit types that keep their own documentation + on the root (workflows, pages) were not tried. diff --git a/contrib/inbox/2026-10-21-testable-retry-loop-shape.md b/contrib/inbox/2026-10-21-testable-retry-loop-shape.md new file mode 100644 index 00000000..ff686d26 --- /dev/null +++ b/contrib/inbox/2026-10-21-testable-retry-loop-shape.md @@ -0,0 +1,80 @@ +# Retry an external call in a microflow: a loop shape that lints clean, and a decision flow that tests without the call + +**From:** a field project (anonymised) — a Mendix 10.24 app built with mxcli v0.27.0 and this toolkit +**Date:** 2026-10-21 +**Kind:** skill-draft +**Field evidence:** the shape retries a call to an external service up to N times in a field app +(68 of 68 of that app's microflow tests passed). It was rebuilt on a synthetic Shop module and +re-run: `mxcli check`, `mxcli lint`, `mx check` in Docker, `mxcli test` in Docker. Mendix 10.24.18, +mxcli v0.27.0. +**Proposed target:** `skills/learned-microflow-patterns.md`, a new section after "Per-row +isolation in a loop" (same family: keep the loop body small) + +--- + +## The problem + +"Call the service; if the reply is bad, call again, up to N times" is usually written as +`while true` with a `return` inside the loop. On mxcli v0.27.0: + +- `mxcli lint` reports MDL-FLOW01 once per `return` inside the loop. Re-run here: two `return`s, + two hits. `mxcli check` and `mx check` (0 errors) were clean, so lint is the only warning. +- A statement placed after a `while true` that has no `break` can never run. `check` accepts it, + `exec` writes it, `mx check` fails with CE0104 (upstream mendixlabs/mxcli#1491, open when this + was written; not re-run for this note). + +## The shape + +Put every decision in one sub-microflow that returns a status string. The loop only repeats the +calls until the status says stop. `Shop.RetryState` is a non-persistent entity with `Attempt` +(Integer, default 0) and `MaxRetries` (Integer, default 3); the counter lives there, so a test can +hand the decision flow any `Attempt`. + +``` +Shop.SUB_CallService() -> String the external call; empty means it failed +Shop.SUB_HandleResponse(Body, State) -> String 'stored' | 'retry' | 'failed' +Shop.ACT_CallWithRetry() -> Boolean the loop +``` + +`SUB_HandleResponse` is the only place the counter changes: + +``` +if $Body != empty and $Body != '' then return 'stored'; +if $State/Attempt < $State/MaxRetries then + change $State (Attempt = $State/Attempt + 1); + return 'retry'; +return 'failed'; +``` + +`ACT_CallWithRetry` has no `return` in the loop: + +``` +$State = create Shop.RetryState (); +declare $Outcome String = 'retry'; +while $Outcome = 'retry' +begin + $Body = call microflow Shop.SUB_CallService (); + $HandleOutcome = call microflow Shop.SUB_HandleResponse (Body = $Body, State = $State); + set $Outcome = $HandleOutcome; +end while; +return $Outcome = 'stored'; +``` + +With `MaxRetries = 3` the service is called at most 4 times: the first call plus 3 retries. + +## What was checked (synthetic module in a copy of a 10.24.18 app) + +- `mxcli check` passed; `mxcli lint` had no hit on the three flows; the `while true` version with + `return true` / `return false` inside gave 2 MDL-FLOW01 hits. +- `mx check` in Docker on a model holding both versions: `The app contains: 0 errors.` +- `mxcli test` in Docker: 5 passed, 0 failed, exit 0. Four tests call `SUB_HandleResponse` with + a chosen `Attempt` and a body of `'ok'` or `''`, and assert the string and the counter: success + (counter stays 0), a retry (counter up), the last allowed retry (`'retry'`, counter 3), the + limit (`'failed'`, counter stays 3). The fifth runs the whole loop against a stub that succeeds. +- With one expectation flipped in a copy, exactly that test failed (`expected $State/Attempt = 2, + actual: 1`) and the exit was 1. + +## Limits + +The tests do not exercise the loop's repeat path, because the stub always succeeds; they prove +the decisions and the wiring. Not checked: how Studio Pro draws the loop.