Sudoku findings: OQL column union, Docker-free mxcli test --local, loop-scope check - #105
Merged
Merged
Conversation
`mxcli lint` on a freshly initialised app reported ~106 issues, of which ~102 came from the System module — Mendix's own platform module, whose entities you cannot document, give access rules, or rename members of. Every one of those findings is un-actionable, and they buried the handful about the developer's own code. Measured on a real project here: 170 issues, 102 of them System, across QUAL002 (50), SEC001 (38), CONV001 (8), DESIGN001 (4), MPR003 and SEC006. `mxcli lint` already had -e/--exclude and a lint-config.yaml with excludeModules, so this was solvable — but only by a user who already knew to look. The default is what people see. `mxcli init` now writes .claude/lint-config.yaml with System excluded, which drops that project from 170 findings to 68. The file is written only when the project has no lint config in any location FindConfigFile searches, so re-running init never discards edits. It is written outside the per-tool branches because `mxcli lint` reads it regardless of which AI tool was selected. Also warns when --modules names a config-excluded module. LintContext .IsExcluded checks the exclude set before the include set, so `lint -m System` against the new default would otherwise report zero findings with no explanation — a trap this change would have introduced. Addresses issuetracker finding #9. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
The Mendix runtime omits a column from a row's JSON object when its value is null. parseOQLFeedback took the column list from the first row only, so a column that happened to be null in row 1 was dropped from the entire result — `mxcli oql` rendered a narrower table than the query asked for, with no error and no empty column to hint at the loss. The column set is now the union of every row's keys. New keys are inserted directly after the last key already known rather than appended, so a column absent from earlier rows keeps its SELECT position: merging [A, C] with [A, B, C] gives [A, B, C], not [A, C, B]. Rows are re-scanned for key order only when they carry a key not seen yet, so the uniform case still costs one length check. Verified by stubbing the union back to first-row-only and watching the reported symptom return. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Referencing a loop's variable after `end loop;` passed `mxcli check` and then failed the build with [error] [CE0108] "Variable 'item' is defined but not in scope at this location." Both flavours reproduce against mxbuild 11.12.1: the iterator itself, and anything the loop body introduces (a retrieve, a `$X = create …`, a call output). MDL053 maps each loop-scoped name to the loop whose own body introduces it (a nested loop keeps its own names), then walks the flow tracking which loops enclose the current position and flags any reference from outside the owner. A name claimed by two loops is deliberately not reported — that is the MDL052/CE0111 duplicate-name case, and without the guard the existing MDL052 negative example started failing for the wrong reason. MDL052 is the sibling rule: names are unique across the whole microflow, but visibility stops at the loop body. The write-microflows skill now states both and shows the carry-out idiom. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
The loop-variable-scope check (MDL053) was filed against sudoku finding #40, which is actually an app bug in that project ("roughly one dealt board in twenty is not solvable by forced logic") and explicitly marked "not an mxcli bug". The check is a real, mxbuild-verified check-parity gap, so it stays — but it is not that finding, and the repro file and symptom row said otherwise. The OQL column fix does match sudoku #39, but only its first half; the second (ORDER BY on a DateTime attribute being ignored) is untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
`mxcli test` got all the way through parsing, runner generation, model
injection, after-startup wiring and the full mxbuild build with no
container, then died on `docker up` — so microflow tests were unavailable
in exactly the environments mxcli targets, where /usr/bin/docker exists
with no daemon behind it.
Only the last step needed a container. --local boots the same standalone
runtime `mxcli run --local` uses (new docker.StartLocalApp: cache mxbuild
+ runtime, ensure the database, mxbuild --serve build, boot) and reads the
runner's output from the runtime log. The Docker path moves to
runDockerAndCapture; both share parse, inject, parse-results and cleanup.
Local runs use their own ports (8081/8091) and a <project>_test database,
so a warm `run --local` loop can keep serving the same project and test
data never lands in the database the developer is looking at.
Two things only running it could have taught:
- The runner reports through an after-startup microflow, so its LOG
output happens during the start action, before the runtime attaches
its log subscriber. Registering the subscriber early is not possible —
the runtime answers LoggingException pre-start. The JVM console tee,
live from spawn, is what carries it; confirmed by A/B running with the
early attach removed, and it is documented where it matters.
- A failing test makes the runner return false, which fails the
after-startup action, which makes `start` return an error. The first
version reported that as a broken run and printed a stack trace
instead of the test report.
Verified end-to-end in a container with no Docker daemon: two tests
passing, then one passing and one failing with exit 1, project restored
both times.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Findings from the sudoku test project, plus one lint-config commit that was pushed to this branch after #100 merged and so was never in a PR.
mxcli oqldropped a whole column (finding 39, first half)The runtime omits a null-valued column from a row's JSON object entirely, and
parseOQLFeedbacktook the column list fromrows[0]. A column that happened to be null in row 1 disappeared from the entire result — no error, no empty column, a well-formed table answering a narrower query than the one asked. The reporter's harness declared 40 of 40 games broken on that basis.The column set is now the union of all rows' keys. New keys are inserted after the last already-known key rather than appended, so a column absent from early rows keeps its SELECT position (
[A,C]⊎[A,B,C]→[A,B,C], not[A,C,B]). Rows are only re-scanned for key order when they carry an unseen key.Proven by stubbing the union back to first-row-only: the three sparse cases fail with exactly the reported symptom.
mxcli test --local— microflow tests without Docker (finding 41)mxcli testgot through parsing, runner generation, model injection, after-startup wiring and the entire mxbuild build natively, then died ondocker up. Microflow tests were unavailable in exactly the environments mxcli targets — containers that ship/usr/bin/dockerwith no daemon.Only the last step needed a container.
docker.StartLocalAppperforms therun --localboot (cache mxbuild + runtime → ensure database →mxbuild --servebuild → standalone runtime); the runner reads its output from the runtime log. The Docker path moved torunDockerAndCapture; both modes share parse, inject, parse-results and cleanup. Local runs use their own ports (8081/8091) and a<project>_testdatabase, so a warmrun --localloop can keep serving the same project and test fixtures never land in the database the developer is watching.Two things only running it could have taught:
LOGoutput happens during the start action, before the runtime attaches its log subscriber. Registering the subscriber early is not possible — the runtime answersLoggingExceptionpre-start. The JVM console tee, live from spawn, is what carries it; confirmed by A/B running with the early attach removed, and the broken option was deleted rather than shipped.startreturns an error. The first version reported that as a broken run and printed a stack trace instead of the test report.Verified end-to-end in a daemon-less container: 2 passed (exit 0); then 1 passed / 1 failed (exit 1), with the project restored both times.
Loop variable used outside its loop → MDL053
Referencing a loop's variable after
end loop;passedmxcli checkand then failed the build. Both flavours reproduce against mxbuild 11.12.1 — the iterator itself, and anything the body introduces:Each loop-scoped name is mapped to the loop whose own body introduces it (nested loops keep their own), then the flow is walked tracking which loops enclose the current position. A name claimed by two loops is deliberately not reported — that is the MDL052/CE0111 duplicate-name case, and without the guard the existing MDL052 negative example failed for the wrong reason. A sweep over all MDL examples shows zero MDL053 hits.
MDL052 is the sibling rule: names are unique across the whole microflow, but visibility stops at the loop body. The write-microflows skill now states both and shows the carry-out idiom.
Attribution note: this was originally filed against sudoku finding 40, which is actually an app bug in that project and explicitly marked "not an mxcli bug". The check is real and verified, but the repro filename and symptom row said otherwise; one commit here corrects that.
Also included
feat(init): seed a lint config that excludes the System module— reviewed and approved during #100, pushed to this branch after that PR merged, so it never landed. Rebased onto currentmainand carried here.Not addressed
Finding 39's second half —
ORDER BYon a DateTime attribute being ignored.ExecuteOQLforwards the query text verbatim, which points at the runtime's preview engine rather than mxcli, but that is a hypothesis and wants a reproduction against a running app with DateTime data before anything is claimed.Validation
make build,go vet,go test ./mdl/... ./sdk/... ./cmd/mxcli/...greenmake check-mdl: 275 examples pass, including the new negative test🤖 Generated with Claude Code
https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Generated by Claude Code