Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
5317ebe
fix(pages): refuse a show_page widget argument where there is no cont…
claude Sep 18, 2026
00a6aaf
Merge remote-tracking branch 'origin/main' into claude/mxcli-issue-10…
claude Sep 18, 2026
385ca83
Merge pull request #519 from ako/claude/mxcli-issue-1029-05wjen
ako Sep 18, 2026
0561516
fix(pages): bind a page-parameter flow argument through Variable, not…
claude Sep 18, 2026
f4779b7
docs: point the current-state docs at the engine that exists
claude Sep 18, 2026
aa79d78
docs(microflows): document the carried properties in the three places…
claude Sep 18, 2026
9a8681b
Merge pull request #522 from ako/claude/mxcli-1140-flow-arg-page-para…
ako Sep 18, 2026
f8c5351
Merge pull request #523 from ako/claude/compassionate-ramanujan-tr79mp
ako Sep 18, 2026
0cbcae1
Merge pull request #537 from ako/claude/nice-goodall-ft9xwr
ako Sep 18, 2026
d947aa8
fix(pages): bind a dynamic image to its entity, and round-trip it
claude Sep 19, 2026
9765c65
feat(microflows): author the URL, export level and concurrency from MDL
claude Sep 19, 2026
8767cbf
Merge pull request #538 from ako/claude/brave-faraday-tuo7z1
ako Sep 19, 2026
46cb368
Merge remote-tracking branch 'origin/main' into claude/nice-goodall-f…
claude Sep 20, 2026
696080b
Merge pull request #539 from ako/claude/nice-goodall-ft9xwr
ako Sep 20, 2026
2202722
fix(odata): never mark an external entity's key attribute Updatable
claude Sep 20, 2026
197ecb4
fix(security): downgrade write rights on an autonumber, not just a ca…
claude Sep 18, 2026
4b39ccf
fix(report): honour lint-config.yaml and share one rule set with lint
claude Sep 18, 2026
c7c93e3
feat(alter-page): SET Documentation on a page, layout or snippet
claude Sep 18, 2026
11a1c6e
feat(security): ALTER PROJECT SECURITY STRICT MODE ON|OFF
claude Sep 18, 2026
d5b7194
test: bug-tests and shared-rule coverage from the branch review
claude Sep 18, 2026
496a649
fix(pages): bind an input widget's attribute over an association
claude Sep 20, 2026
bb01f45
fix(odata): gate the key-updatable rule on isTopLevel
claude Sep 20, 2026
8f1e865
Merge pull request #540 from ako/claude/gifted-curie-gmigzy
ako Sep 20, 2026
5dbd930
Merge remote-tracking branch 'origin/main' into claude/funny-keller-r…
claude Sep 20, 2026
a6db623
Merge pull request #542 from ako/claude/funny-keller-rrm17w
ako Sep 20, 2026
3c19cce
fix(odata): a top-level external entity has no updatable attributes
claude Sep 20, 2026
064fa4c
Merge pull request #543 from ako/claude/gifted-curie-gmigzy
ako Sep 20, 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
3 changes: 3 additions & 0 deletions .claude/commands/mxcli-dev/review.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ proactively. Add a row after every review that surfaces something new.
| 26 | A clause added to a SHARED grammar rule (a datasource, a widget-property list) is written by only ONE of the constructs that rule serves — the others parse it, `exec` reports success, and DESCRIBE does not echo it back. A silent drop, often shipped by the very change that was fixing silent drops | Code correctness | Enumerate the other constructs the rule serves and RUN one. The round-trip that proved the feature on its intended target says nothing about them. Refuse it where it cannot be stored, naming the construct that can — an error, not a warning, when the metamodel decides it and no future package can make it valid |
| 27 | A metamodel-sync or list-coverage test asserts that a GAP still exists (`clickCapableInMendix["listview"]`, "a template for the list view's own entity is the base case Mendix permits") — so it passes throughout and FAILS on the correct fix, and the belief it encodes was never measured | Test coverage | Invert such a test rather than deleting it: keep the half that is still true (the metamodel really does carry the field) and flip the half that is not. When a test justifies itself by what a helper returns rather than by a measurement, treat it as a claim to check, not as evidence |
| 28 | A describe emitter added beside a shared property formatter duplicates a field the formatter already prints (`Editable: true` twice on one widget) — invisible when the round-trip only covers the page the change was written against | DESCRIBE roundtrip | Round-trip a page OTHER than the one under test, and assert occurrence COUNT (`strings.Count(out, x) != 1`), not presence. `Unchanged page` on re-exec of the describe output is the evidence that the emitted MDL rebuilds the stored document; `Check passed!` is not |
| 29 | A predicate that names ONE cause of a build error is read as if it named the error (`mem.IsCalculated` for CE6592, which an autonumber also triggers) — the half that is covered works, so every test passes and the gap is invisible until a user hits the other half | Code correctness | When a guard cites a CE number, enumerate what the PLATFORM rejects, not what the current code checks. Put the rule in one named place (`types.WriteRightsForbidden`) rather than a bare boolean at each site, so the second cause has somewhere to go. And fix every pass that can re-derive the value — a reconcile running after every program re-broke a grant the user had corrected by hand |
| 30 | Two commands compute the same thing from two copies of the setup (`report` re-implementing `lint`'s rule list and skipping its config), so they disagree about a project — and a SCORE carries no provenance, so neither number looks wrong | Code correctness | Extract the shared setup and route both through it. A value test cannot guard this when the copies live inside cobra `RunE` bodies: use a structural check on the source, with a positive control asserted FIRST so it cannot pass vacuously |
| 31 | A test helper that needs a heavyweight object only to satisfy a signature (`NewLintContext(nil, nil)`, which panics) invites a nil-guard added purely to make the test compile — behaviour nothing in production needs, defended forever | Test coverage | Narrow the signature instead: if the helper does not use the parameter, drop it and let the caller apply the part it owns. A test that cannot construct an argument is usually telling you the argument does not belong |

---

Expand Down
3 changes: 1 addition & 2 deletions .claude/lint-rules/sec_strict_mode.star
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,5 @@ def check():
return [violation(
message="Strict mode is disabled. This weakens XPath constraint enforcement and is relevant to CVE-2023-23835.",
location=location(module="", document_type="security", document_name="ProjectSecurity"),
# mxcli/MDL cannot toggle strict mode — it is a Studio Pro-only setting.
suggestion="Enable strict mode in Studio Pro: Project Security > Enable 'Check security' and turn on strict-mode XPath validation (not settable via MDL).",
suggestion="Enable it with: ALTER PROJECT SECURITY STRICT MODE ON; (or in Studio Pro under Project Security).",
)]
1 change: 1 addition & 0 deletions .claude/skills/fix-issue/findings/cmd-mxcli.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,4 @@
{"area": "cmd/mxcli", "date": "2026-09-17", "symptom": "`mxcli run --local` on a project with Settings > Web UI > OptimizedClient = No exits 1 after the cold build with `no rollup.config.mjs and no bundle at .../web/dist/index.js ... the build did not produce a client` \u2014 about a deployment whose client is sitting in the same directory. `mxcli docker run` works", "cause": "The client gate tested for exactly two shapes (a rollup config to run, or a bundle mxbuild already wrote) and treated everything else as a failed build. A classic (Dojo) deployment is a legitimate third shape with NEITHER: there is no bundling step for the classic client in any Mendix version. Nothing in the local loop read UseOptimizedClient, so the mode was invisible", "file": "`cmd/mxcli/docker/webclient_plan.go` (new: `planWebClient`, `isClassicWebClient`, `noWebClientError`) + `webclient.go` (`BuildWebClient`, `ensureWebClientBundle`) + `webclient_watch.go` (`StartWebClientWatch`) + `runlocal.go` (`ensureClientServed`)", "insight": "Measured on one blank 11.12.2 app built twice, changing only the setting: **mxbuild swaps which client lands in `deployment/web/` and parks the other beside it** \u2014 OptimizedClient=Yes gives `web/` the React client (+`rollup.config.mjs`) and parks Dojo in `dojo-web/`; =No gives `web/` the Dojo client (`index.html` loading `mxclientsystem/mxui/mxui.js`, no rollup config, no dist) and parks React in `react-web/` WITH its own rollup config. So detect from the DEPLOYMENT, not from the model's setting: the deployment is what gets served, the two disagree exactly when the setting has just changed, it needs no plumbing through the five call sites, and it covers MigrationMode without predicting what that mode emits. Detect on POSITIVE evidence (the entry point names its client) \u2014 inferring classic from the absence of the React shapes would make every genuinely broken deployment look classic and silently skip the bundle, which is the black screen the gate exists to prevent; keep a control test that a clientless deployment still fails. **The gate had FIVE consumers, not one**: boot, the `--watch` bundler, the post-boot re-bundle guard, and `ensureClientServed`, which probes that `/dist/index.js` is *served* \u2014 measured 404 on a classic app, so fixing only the boot moves the failure to every applied change under `--watch`. Two of them carried hand-copied duplicates of the same gate and had already drifted once (the 11.14 fix, ako/mxcli-ledger #146, landed on `BuildWebClient` only, so `run --local` worked on 11.14 and `run --local --watch` did not) \u2014 so the fix collapses them into one `planWebClient` rather than adding a third copy. Repro from Linux with no Mac and no Studio Pro: `mxcli new` an 11.12.2 app, flip `UseOptimizedClient` to `No` on `Forms$WebUIProjectSettingsPart` (note the `Forms$` prefix, not `Settings$`), build, and keep the unflipped copy as the control. Patching that BSON with a Go `map` corrupts the file \u2014 mxbuild refuses it with `Expected '$ID' as the first property of a storage object` \u2014 because map iteration loses key order; use `bson.D` throughout. Verified in a browser, not just at the gate: `mx` global present, real page content, zero console errors. Issue #1123", "refs": ["#1123", "ako/mxcli-ledger#146"]}
{"area": "cmd-mxcli", "date": "2026-09-17", "symptom": "`mxcli new --version 10.24.25` (and `mxcli setup mxbuild --version 10.24.25`) dies with `HTTP 404 from https://cdn.mendix.com/runtime/mxbuild-10.24.25.tar.gz`. Every 9.x and 10.x version probed 404s while 11.6.0/11.12.1/11.13.0 return 200 from the same host and path, which reads as 'Mendix 10 is no longer on the CDN'.", "cause": "Mendix 9 and 10 publish FOUR-part artifact names carrying a build number the release notes never mention: the release called 10.24.25 is `mxbuild-10.24.25.122571.tar.gz`. Mendix 11 publishes three parts. `MxBuildCDNURL` interpolates whatever string it is handed and nothing resolved a partial version, so a hand-typed 10.x version named no artifact at all. Project-driven paths were never affected — the MPR's `_ProductVersion` already carries all four parts (`10.24.25.122571`) and `parseVersion` takes the first three for major/minor/patch while the full string goes to the URL.", "file": "`cmd/mxcli/docker/version_resolve.go` (ResolveCDNVersion, highestBuild, CDNReleasesFor); wired at the two entry points where a user types a version, `cmd/mxcli/cmd_new.go` and `cmd/mxcli/setup.go`. Tests `cmd/mxcli/docker/version_resolve_test.go`.", "insight": "**A uniform 404 across a whole major version is evidence about the NAME, not about availability.** The conclusion drawn from it — 'Mendix 10 cannot be downloaded here' — blocked a verification for an entire session, and the fix was one listing call: the CDN is an S3 bucket that answers ListObjectsV2 (`?list-type=2&prefix=runtime/mxbuild-10.24.`), so what exists is enumerable rather than guessable. When a probe fails identically for every input in a class, question the query before concluding the class is empty. Three traps in the resolution itself, each a test: the `.sha256` sidecar beside every archive must not be picked as an artifact; the prefix needs its trailing dot or `10.24.2` swallows `10.24.20`..`10.24.26`; and build numbers are not zero-padded, so a text sort puts 99999 above 122571 and 10.24.9 above 10.24.26. Resolve at the entry point and thread the RESOLVED string onward — `mxcli new` checks the created project's stamp against the requested version, and `mx create-project` stamps four parts, so resolving late would fail that postcondition.", "refs": ["#1121"]}
{"area": "cmd/mxcli", "date": "2026-09-18", "symptom": "mendixlabs/mxcli#1025: `mxcli syntax` advertises `mxcli syntax workflow user-task targeting` in its own help and answers `Unknown topic: workflow user-task targeting`. Same for `workflow user-task` and `workflow parallel-split`, all of which `mxcli syntax workflow` lists as sub-topics; `--json` was the only route that reached them.", "cause": "The CLI built its path with `strings.Join(args, \".\")` and never split an argument, so a topic handed over as ONE string — a quoted copy-paste, a tool wrapper, `sh -c` — became the path `workflow user-task targeting`, which matches nothing. The REPL's `help` had resolved multi-word topics since it was written (`resolveHelpPath`, greedy hyphen-joining): one question, two answers, and the CLI held the weaker copy. The #955 segment-match fallback could not save it either — it passed the DOTTED path to `BySegmentMatch`, and no segment contains a '.', so that fallback was silently dead for every multi-word query.", "file": "cmd/mxcli/syntax/topic.go (new: Lookup, topicWords, resolvePath), cmd/mxcli/help.go, mdl/executor/cmd_misc.go (resolveHelpPath deleted), mdl/grammar/domains/MDLSettings.g4 (helpStatement, helpTopicWord), mdl/visitor/visitor_query.go (ExitHelpStatement); tests cmd/mxcli/cmd_syntax_test.go, cmd/mxcli/syntax/topic_test.go, mdl/executor/cmd_misc_test.go, mdl/visitor/visitor_help_topic_test.go; example mdl-examples/bug-tests/syntax-1025-topic-drilldown.mdl", "insight": "**The spaces in the reported error message were the whole diagnosis, and reading them as a paraphrase cost an hour.** The command prints the path it built, and the CLI joins on '.', so `Unknown topic: workflow user-task targeting` cannot come from the command as documented — it can only come from the topic arriving as a single argument. Every line of the report follows from that and nothing else does: `syntax workflow` works (one word), `--json` works (the flag is not part of the topic), the three multi-word forms fail. Take a quoted error message literally, character for character, before assuming the reporter retyped it. **The reported version is downloadable and settles it in one run**: `mxcli setup mxcli`'s own URL shape (`releases/download/<tag>/mxcli-linux-amd64`, NOT the goreleaser `_Linux_x86_64.tar.gz` that 404s) fetched v0.20.0, where the unquoted command works and the quoted one reproduces the message verbatim — so 'fixed since' and 'never broken' were both wrong. **The guard that matters is not the three cases from the report** but `TestEveryRegisteredPathIsReachableBySpelling`: every registered path, tried dotted, as separate arguments, and as one string. The registry prints dotted paths and then tells the reader to drill down with words, so a spelling that does not resolve is the command contradicting its own output; a per-case test would have passed the day someone added a topic with a new shape. Control: stub the whitespace split in `topicWords` and it fails with the reported path, spaces and all. **The grammar half has a trap the CLI half does not, and only the EXISTING suite caught it.** `helpStatement: IDENTIFIER (identifierOrKeyword)*` is the grammar's catch-all — a statement that is just an identifier and some words — so whatever it can swallow, it swallows from the statement that should have had it. Widening it to `(DOT? helpTopicWord)*` to take `help workflow.user-task` made `Sec.ApiUser` a complete statement of its own, and `create module role Sec.ApiUser` then parsed, WITH NO PARSE ERROR, as CREATE MODULE (named \"role\") followed by a help topic — two statements, wrong types, six unrelated security tests red. `(helpTopicWord (DOT? helpTopicWord)*)?` — a topic word before any dot — leaves `.ApiUser` unconsumable and restores the old disambiguation. Bisect a grammar regression by SHAPE, not by reading the ATN: adding the unused rule alone was clean, the hyphen alone was clean, the leading optional DOT was the whole of it, and three regenerations said so in about a minute. **When widening a permissive rule, the test to add is not for the new spelling but for what the rule must still NOT swallow** (TestHelpRuleDoesNotSwallowATrailingQualifiedName).", "refs": ["mendixlabs/mxcli#1025", "#955"]}
{"area": "cmd/mxcli", "date": "2026-09-18", "symptom": "`mxcli report` scores a project against rules the team disabled in `lint-config.yaml`. `mxcli lint` honours the config, the report's SCORE does not move, so the score cannot be calibrated at all. Reported at 66/100 against a 99/100 blank-app baseline, where 61 of 86 findings were two deliberately-accepted rules", "cause": "`cmd_report.go` never called `linter.FindConfigFile`/`LoadConfig` — it went straight from `linter.New` to `BuildReport`. Separately it carried its own INLINE copy of the built-in rule list, one rule behind `builtinLintRules()` (missing MDL-FLOW01), so the two commands scored one project against two rule sets. One root cause: report re-implemented lint's setup instead of sharing it", "file": "`cmd/mxcli/cmd_report.go`, `cmd/mxcli/cmd_lint.go`, new `cmd/mxcli/lint_setup.go` (`projectLintRules`, `applyLintConfig`), `mdl/linter/linter.go` (`RuleEnabled`)", "insight": "Same class as #904 in the opposite direction: there a silently reduced rule set made the score falsely HIGH, here an unread config makes it falsely LOW — and both are invisible because a score carries no provenance. **A value test cannot guard the inline copy**: both commands build rules inside a cobra RunE, so nothing a unit test can call notices a second list being re-added. The guard is therefore structural — grep `cmd_report.go` for `lint.AddRule(rules.New` — with a POSITIVE CONTROL first (assert `builtinLintRules` still constructs rules) so it cannot pass vacuously, the same shape as `scripts/check-tunnel-deps.sh`. Take the LintContext out of `applyLintConfig`'s signature: `NewLintContext(nil, nil)` panics, and a nil-guard added only to make a test compile is how a helper acquires behaviour nothing needs", "refs": ["#525", "#904"]}
Loading
Loading