Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
03e3404
fix(test): a later passing @expect no longer clears an earlier failur…
claude Oct 28, 2026
819d3ce
fix(test): rename a lower-case `call`/`create` variable per test on t…
claude Oct 28, 2026
68e0d38
fix(security): keep a module role's description when create or modify…
claude Oct 28, 2026
3c17e99
fix(modelsdk): declare AggregateFunction on Mendix 9, floor Reduce's …
claude Oct 28, 2026
890899d
fix(canon): write no workflow PersistentId below Mendix 10.21
claude Oct 28, 2026
238f9de
fix(canon): complete a workflow's state-change handlers on Mendix 9.24
claude Oct 28, 2026
2a76e0e
fix(canon): write a constant override flat below Mendix 10.9
claude Oct 28, 2026
2d87eb9
fix(canon): leave a $currentObject flow argument out below Mendix 10.10
claude Oct 28, 2026
16c1c7f
fix(executor): gate create published odata service at Mendix 10.4
claude Oct 28, 2026
1b62f21
docs(wiki): a version refusal is not always an older shape
claude Oct 28, 2026
1efb8c0
Merge pull request #1764 from ako/claude/loving-clarke-qun7w5
ako Oct 28, 2026
714fdf4
fix(test): git doctor tests no longer read the developer's git config
claude Oct 28, 2026
63c637b
Merge pull request #1765 from ako/claude/loving-clarke-qun7w5
ako Oct 28, 2026
b927395
fix(make): fmt-check uses the toolchain's gofmt, not the one on PATH
claude Oct 28, 2026
e1d6cc9
build(make): add ci-local, the build-and-test job run before pushing
claude Oct 28, 2026
76d34ab
docs(skills): add the steward skill, the loop from PR opened to merged
claude Oct 28, 2026
982c4b1
fix(git): doctor judges notes only once the remote's notes are fetched
claude Oct 28, 2026
16eac21
fix(git): doctor judges the configured remote URL, not the insteadOf …
claude Oct 28, 2026
0081f56
fix(git): commit --dry-run leaves what the user staged as it was
claude Oct 28, 2026
e438d6c
fix(git): commit leaves out only the project's own build folders
claude Oct 28, 2026
9ff093a
Merge pull request #1766 from ako/claude/loving-clarke-qun7w5
ako Oct 28, 2026
e4bb180
Merge pull request #1767 from ako/claude/optimistic-fermi-x62bln
ako Oct 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "cmd/mxcli", "date": "2026-10-28", "symptom": "`mxcli git commit` silently leaves a user file out of the commit when any folder on its path is named like a build folder: `resources/deployment/endpoints.xml` (or `javascriptsource/theme-cache/x.js`) stays untracked after the commit, with only `Left out 1 file(s) Studio Pro never commits (project-settings.user.json, *.mpr.lock, caches)` to show for it", "cause": "isJunkPath matched `.mendix-cache`, `deployment`, `theme-cache` and `.mxcli` on every path segment, so any folder of that name at any depth was dropped. Those are the project's own folders, the ones Studio Pro, mxbuild and mxcli create directly beside the .mpr; the file-name rules (lock, backup, user settings) are the ones that rightly match at any level", "file": "`cmd/mxcli/cmd_git_commit.go` (`isJunkPath(rel, projDir)`, projDir from `path.Dir(gitNoteRepo.MPRPath)`, which already resolves the project folder inside the repository, symlinks included); tests `TestGitCommit_NestedFolderNamedLikeJunkIsCommitted`, `TestGitCommit_JunkAnchoredAtProjectInSubfolder` in `cmd/mxcli/gitdoctor_test.go`", "insight": "**Anchor a path rule where the thing it names is created**: the build and cache folders only ever appear beside the .mpr, and a rule that matches the name at every depth also catches user folders that share it. **The report that something was left out did not name it**, which is what made the drop silent; a nested-folder fixture next to the root one (the existing test only had root `deployment/`) is the control that tells the two apart. Test the project-in-a-subfolder layout too: the anchor is the project's folder, not the repository root, and the same names outside the project are not its own", "refs": ["ako/mxcli#1670", "mendixlabs/mxcli#1669"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "cmd/mxcli", "date": "2026-10-28", "symptom": "`mxcli git commit -m … -p app.mpr --dry-run` unstages everything the user had staged: a file added with `git add` before the dry run is no longer staged afterwards (`git diff --cached` is empty), although the command committed nothing and documents that a dry run writes nothing", "cause": "The dry run staged everything in the real index (`git add -A`) to report it, then undid that with a plain `git reset -q`, which resets the index to HEAD rather than to what it was before the run", "file": "`cmd/mxcli/cmd_git_commit.go` (`commitRun`: a dry run stages into a temporary copy of the index through GIT_INDEX_FILE, `indexCopy`); `cmd/mxcli/gitnote.go` (`gitOutInEnv`); test `TestGitCommit_DryRunKeepsTheUsersStaging` in `cmd/mxcli/gitdoctor_test.go`", "insight": "**The existing dry-run test started from an empty index**, so 'nothing staged afterwards' was both the correct result and the bug's result; a test of an undo has to start from a state the undo can get wrong. **Do not undo, isolate**: staging into a GIT_INDEX_FILE copy leaves the real index untouched byte for byte (stat data and intent-to-add entries included), which no reset or write-tree/read-tree round trip quite does. An empty file is not a valid index, so with no index yet the temporary path is left free for git to create", "refs": ["ako/mxcli#1670", "mendixlabs/mxcli#1669"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "cmd/mxcli", "date": "2026-10-28", "symptom": "`mxcli git doctor` on a fresh clone (or any CI checkout) reports every commit as `[ ] <sha> without a note` and exits 1, while the same report says `[x] origin has refs/notes/mx_metadata`; `--fix` then writes computed notes over commits the remote already annotates, and the `git push origin <branch> refs/notes/mx_metadata` it tells you to run is rejected as non-fast-forward", "cause": "doctorNotes judged each commit against the LOCAL refs/notes/mx_metadata only, and nothing fetched it: git clone and a plain git fetch never fetch refs/notes/*. The remote check had already run `git ls-remote <remote> refs/notes/mx_metadata` and printed the tip, but that result was not passed on, so an empty local ref read as 'no commit has a note'", "file": "`cmd/mxcli/gitdoctor.go` (`doctorRun` passes the remote's notes commit to `doctorNotes`; `hasNotesCommit`: the local ref is, or descends from, that commit); test `TestGitDoctor_UnfetchedNotesAreNotMissing` in `cmd/mxcli/gitdoctor_test.go`", "insight": "**Every existing notes test built its repository with `git init`, never `git clone`**, so the local notes ref was always the only one there was; the defect lives exactly in the gap between a repository that writes notes and one that receives them. A fixture with a bare remote, a pushed note and a fresh clone reproduced it on the first run. **Do not judge, and do not write, against a ref that is behind the remote**: Studio Pro's own fetch force-replaces the local notes ref with the remote's, so the remote's notes are the ones that count; a note written over a remote-annotated commit is either refused on push or thrown away on Studio Pro's next fetch. **Fetch as a fast-forward only** (`git fetch <remote> refs/notes/mx_metadata:refs/notes/mx_metadata`, no `+`): it never discards a note that exists only locally, which a force-fetch would; when the local ref has diverged the fetch is refused and the doctor says so instead of writing. Report mode stays read-only: it names the unfetched ref and the command rather than fetching", "refs": ["ako/mxcli#1670", "mendixlabs/mxcli#1669"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "cmd/mxcli", "date": "2026-10-28", "symptom": "With a global `url.git@github.com:.insteadOf https://github.com/` rule (a common developer setup), `mxcli git doctor` reports an HTTPS remote as `origin: git@github.com:org/app.git — SSH; Studio Pro needs HTTPS`, exits 1, and `--fix` \"rewrites\" the configured URL to HTTPS on every run (dropping its `.git` suffix) without changing anything git uses", "cause": "The remote check read `git remote get-url`, which applies url.<base>.insteadOf, and --fix set `remote.<name>.url`, which the rule rewrites again on the next read, so the check could never pass. The same read made the two SSH tests depend on the developer's and the sandbox's git config (ako/mxcli#1765 isolated the tests; the product question stayed open)", "file": "`cmd/mxcli/gitdoctor.go` (`doctorRun`: judge `git config --get remote.<name>.url`; report a differing `git remote get-url` as an [i] insteadOf line); tests `TestGitDoctor_InsteadOfRewriteIsNotAnSSHRemote`, `TestGitDoctor_ConfiguredSSHRemoteFoundBehindRewrite` in `cmd/mxcli/gitdoctor_test.go`", "insight": "**A check and its fix must read and write the same thing.** `remote get-url` is the effective URL, `remote set-url` writes the configured one; with a rewrite in between, a fix can never make its own check pass, and a loop like that is the tell. Judge what you can change (the configured URL) and report what you cannot (the rule, which applies to every git command on the machine). **Put the rule in the test repository's own config and use a host nobody rewrites** (git.example.com): the test then shows the behaviour without inheriting the developer's or the CI sandbox's rewrites of github.com, which point in opposite directions. Set GIT_SSH_COMMAND=false when the effective URL is SSH, or ls-remote reaches for the network", "refs": ["ako/mxcli#1670", "ako/mxcli#1765"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area":"cmd/mxcli","date":"2026-10-28","symptom":"`make test` failed on main right after #1670 (git doctor) merged, in a hosted sandbox only: TestGitDoctor_SSHRemoteRewrittenByFix and TestGitDoctor_SSHURLFormRewrittenByFix reported `remote url = \"https://github.com/org/app.git\\n\", want https://github.com/org/app`; CI was green","cause":"`git remote get-url` applies url.<base>.insteadOf, and the tests ran git under the developer's own configuration. The sandbox sets GIT_CONFIG_COUNT/KEY/VALUE rewriting git@github.com: and ssh://git@github.com/ to https://github.com/, so the SSH remote the test added already read back as HTTPS and --fix never touched it; a ~/.gitconfig rewriting https://github.com/ to git@github.com: (the usual developer setup) fails the same two tests the other way, `git@github.com:org/app`","file":"`cmd/mxcli/gitdoctor_test.go` (`doctorEnv`: GIT_CONFIG_GLOBAL=os.DevNull, GIT_CONFIG_NOSYSTEM=1, GIT_CONFIG_COUNT=0)","insight":"A test that shells out to git inherits three configuration sources besides the repository's — system, global, and the GIT_CONFIG_COUNT environment — and isolating HOME or the identity variables covers none of the third. Reproduce each source separately, because they fail in opposite directions: env rewrite on this host, then `env -u GIT_CONFIG_* HOME=<dir with an https->git@ insteadOf>`, with an empty HOME as the passing control. Both failed before the fix and pass after. Not fixed, and a product question rather than a test one: with a global https->git@ rewrite, `git doctor` reads the effective URL, so it reports SSH and `--fix` sets an HTTPS URL that the rewrite turns straight back into SSH, every run","refs":["ako/mxcli#1670"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "cmd/mxcli", "date": "2026-10-28", "symptom": "On the after-startup runner (every Docker run, and `--local --legacy-runner`) a test with more than one `@expect` is reported PASS when an earlier assertion fails and a later one passes, and `mxcli test` exits 0. Measured at unit level: log FAIL:test_1:Expected $r = 1, actual: 2 then PASS:test_1 gave status=PASS message=\"\" AllPassed=true. PASS then FAIL reported correctly; the endpoint runner (`--local`, `--attach`, `--watch`) was never affected", "cause": "`writeExpectAssertion` logs a verdict line per assertion (MXTEST:PASS or MXTEST:FAIL), and `ParseLogResults` treated every line as the test's verdict: each case overwrote Status, and the PASS case also cleared Message, so the last line won. The generated flow did compute `$AllPassed = false` and return it — nothing reads that value", "file": "`cmd/mxcli/testrunner/results.go` (`ParseLogResults`, new `applyVerdictLine`)", "insight": "A log protocol that emits several partial verdicts per test needs a monotonic fold, not last-write-wins: FAIL/ERROR is sticky, the first failure's message is reported (the endpoint runner's semantics — its assertions run only while $Verdict is PASS and its skipped-assertions guard never replaces a failure), and further failures are counted onto the message. The plausible wrong turn is 'first verdict line wins': the skipped-assertions guard (#1448) logs its FAIL AFTER the PASS lines of the assertions that did run, so first-wins turns that false green back on — sticky failure, not first line, is the rule. Fixed in the parser rather than the generator: it also corrects logs from runners generated before the fix, and a one-verdict-per-test generator would change a flow shape whose verdicts were measured on 11.13.0 for no additional coverage, since parser and generator ship in one binary. A RUN line resets the fold so a failure cannot outlive the run it was logged in. Second time this parser took a verdict from the wrong line (after #1448's RUN-without-verdict read as the zero status, PASS): when touching `ParseLogResults`, enumerate the line ORDERS the generator can produce per test, not just the line kinds", "refs": ["ako/mxcli#1753", "ako/mxcli#1448"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "cmd/mxcli", "date": "2026-10-28", "symptom": "On the after-startup runner (Docker, `--local --legacy-runner`) two tests in one file that both assign the same variable with a lower-case keyword — `$r = call microflow …`, mdl 1's and `fmt`'s spelling — fail injection with MDL063 (CE0111 \"Duplicate variable name\" from mxbuild); the same file runs under the endpoint runner, which gives each test its own flow", "cause": "The legacy runner compiles every test into one microflow and renames each test's variables with a `_N` suffix, but which variables to rename was decided by `assignPattern`, which matched only upper-case `CALL|CREATE` — the spelling of the runner's own early examples. A lower-case assignment was left unrenamed and declared once per test", "file": "`cmd/mxcli/testrunner/generator.go` (`assignPattern`, now `(?mi)…(?:call|create)\\b`)", "insight": "The cheapest detector was a sweep, not a reading: every `.test.mdl` in the repo through `GenerateTestRunner` and `executor.ValidateProgram` — 10 of the 16 in mdl-examples/ drew MDL063 before the fix, 2 after. Those 2 (`retrieve $Brands`, `retrieve $Request` + `$Request = head(…)`) are the remaining, separate gap: `assignPattern` never matched `retrieve $x`, `declare $x`, list operations (`$x = filter(…)`), `send rest request` or `execute database query` in ANY case, so a variable those define in two tests still collides; the durable fix is to take the declared names from the parsed body (`visitor.FlowStatements`, as `attachOnError` already does), not a regex. Two existing tests (`TestThrowsCatchesEveryCallKind`, `TestVerdictCatchesEveryCallKind`) matched the legacy output against the unrenamed lower-case statement and passed only because of the defect — a test that asserts a generator's text verbatim can encode the bug it should catch. The `\\b` matters once the match is case-insensitive (`$r = callback`). Found alongside ako/mxcli#1753, whose bug-test therefore uses a distinct variable per test", "refs": ["ako/mxcli#1753"], "rules": ["MDL063"], "ce": ["CE0111"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area":"mdl/executor","date":"2026-10-28","symptom":"A bare `create or modify module role M.R;` (no description clause) reported `Modified module role: M.R` and erased the role's stored Description; `describe` → `exec` was unaffected because describe prints the description, and grants (on the entity, keyed by role name) were untouched.","cause":"CreateModuleRoleStmt.Description was a plain string, so 'no clause' and `description ''` were the same value, and execCreateModuleRole's OR MODIFY branch passed it to Backend.AddModuleRole, which overwrites the existing role in place.","file":"mdl/ast/ast_security.go (Description *string), mdl/visitor/visitor_security.go, mdl/executor/cmd_security_write.go (execCreateModuleRole)","fix":"Description is a *string, nil when unstated, as CreateUserRoleStmt's properties have been since ako/mxcli#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.","insight":"A neighbouring statement already had the right shape: the user-role code even said 'the rest keep their stored values, as a module role's missing description does', which was false — a comment asserting a sibling's behaviour is a claim to test, not evidence. The tell for this class is an optional clause parsed into a value type: grep the AST for optional clauses stored as string/bool without a *T or a ...Set flag, then follow each to an OR MODIFY branch. mx check is 0 errors with the fault (measured, 11.13.0), and describe → exec cannot show it because describe restates the value; only a hand-written minimal statement does.","refs":["ako/mxcli#1218"],"test":"mdl/executor/issue1218_module_role_description_test.go, mdl/visitor/visitor_security_test.go"}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/executor", "date": "2026-10-28", "symptom": "On Mendix 9.x `create published odata service` was refused at write time with `this Mendix 9.24.30 project cannot store ODataPublish$PublishedODataService2.IncludeMetadataByDefault (Mendix 10.8.0+)` (10-odata and 18-folder on 9.24), after the earlier statements of the script were already written", "cause": "mxcli writes a published OData service as ODataPublish$PublishedODataService2, a document type introduced in 10.4.0; before it the service is Rest$PublishedODataService (deleted 10.4.0), a different document with its own children. The generated version data has no type-level floor, so the property floor could only trip over the first versioned key and named the wrong thing", "file": "`mdl/executor/version_gates.go` (CreateODataServiceStmt gate), `sdk/versions/mendix-10.yaml` / `mendix-11.yaml` (integration.published_odata_service 10.4.0)", "insight": "**Before lowering a refused property, check the type it sits on exists in that version.** Lowering IncludeMetadataByDefault was the obvious fix and would have made it worse: with the floor off, mx check 9.24.30 stops loading the project at all (`TypeCacheUnknownTypeException: The type cache does not contain a type with qualified name ODataPublish$PublishedODataService2`). mendixmodelsdk says it directly: `PublishedODataService2.versionInfo = {introduced: \"10.4.0\"}`, `rest.PublishedODataService` `deleted: \"10.4.0\"`. The generated tables carry property floors only, so a whole-type floor has to be a version gate (check-time, MDL-VERSION01, before anything is written) — the same gap lets Workflows$EndOfParallelSplitPathActivity (10.21) through on 9.x. Measured: 10-odata and 18-folder on 9.24 now fail at the gate (no write); on 10.24.24 and 11.13 both PASS. Tests: TestCheckVersionGates_ReportsEveryHandlerGate (published odata on 9.24.30 and 10.3), TestCheckVersionGates_UngatedFormsPass (10.4 passes); mdl-examples/bug-tests/1163-published-odata-below-10-4.mdl", "refs": ["#1163", "#1434"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "modelsdk", "date": "2026-10-28", "symptom": "On Mendix 9.x every count/sum/average/minimum/maximum in a microflow was refused: `this Mendix 9.24.30 project cannot store Microflows$AggregateAction.AggregateFunction (Mendix 10.0.0+)` (02-microflow-examples on 9.24)", "cause": "The vendored generated version data (modelsdk/gen/microflows/version.go) gave aggregateFunction the row of reduceReturnDataType ({Introduced 10.0.0, Required}) and had no row for reduceReturnDataType, so the floor refused the one key every aggregate has and let the fold's return type through unrefused", "file": "`modelsdk/gen/microflows/version.go` (Microflows$AggregateAction, fixed by hand), `modelsdk/version/metamodel_versions_gen.go` (go generate ./modelsdk/version)", "insight": "**A refusal can be the floor's data being wrong, not the writer's shape.** The prompt to look for an 'older shape' was a guess; evidence settled it in two steps: mendixmodelsdk 4.117.0 has `aggregateFunction: {}` (no version) and even sets its default differently either side of 9.24.0, and Studio Pro-authored 9.24.0 bytes (Workflow Commons 2.6.0 from the Marketplace, `mxcli marketplace download 117066 --version 2.6.0`, project.mpr inside the .mpk) store `AggregateFunction: \"Count\"` on 10 of 10 aggregates. A one-off script comparing every SDK StructureVersionInfo `introduced` with modelsdk/gen/*/version.go found only 4 mismatches in the whole table (this one; CustomWidgets$WidgetReturnType.type 9.20 vs none; Forms$GroupBox.caption 8.10 vs none; Workflows$CallMicroflowTask.boundaryEvents), so the generator's misattribution is rare, not systematic. **The cheapest 'which keys does version X declare' oracle is a floor-off build + `mx convert -p` on that version**: build mxcli with StripUndeclaredProperties stubbed, exec, dump units, convert, dump again, diff key sets per $Type — 9.24's convert kept all of AggregateAction's keys. Measured: 5 aggregates on a fresh 9.24.30 app → exec ok, mx check 0 errors, mx convert strips nothing (before: refused). Tests: TestNormalize_AggregateFunctionIsDeclaredOnMendix9, TestNormalize_ReduceFoldIsRefusedBelowMendix10; mdl-examples/bug-tests/1163-aggregate-on-mendix9.mdl", "refs": ["#1163"]}
Loading
Loading