From 3c94e096fd1b54da6a146d4b061e27c8ecae7fa3 Mon Sep 17 00:00:00 2001 From: Ako Date: Fri, 4 Sep 2026 11:36:55 +0000 Subject: [PATCH] fix(executor): version-gate the four doctypes that need Mendix 11.9 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The nightly is red on 10.24 and 11.6.8 while 11.12, 11.13 and 11.14 are green. Four of the 29 #1018 documentation fixtures cover doctypes that do not exist below 11.9 — ai model, knowledge base, consumed mcp service, agent — and had no version gate, so on an older project they failed at CREATE: --- FAIL: TestDocumentation_SurvivesRewrite/ai_model create: create model requires Mendix 11.9.0+ (project is 11.6.8) That failure says nothing about documentation carry. The doctype is simply absent, which is what a gate is for. Reproduced locally rather than reasoned about: mxbuild 11.6.0 is already cached, and MX_BINARY pins it, giving the identical message and line in four seconds. Both controls run: 11.6.0 4 SKIP, 25 run and pass, both control tests pass 11.13.0 no skips — all four actually run and pass The second one is the one that matters. A gate that always skipped would turn the whole matrix green while testing nothing, and nothing else would notice. The minimum is carried as a version on the case rather than a boolean, so the reason is legible where the case is written and it mirrors the registry entries in sdk/versions/mendix-11.yaml (agent_model, agent_knowledge_base, agent_consumed_mcp_service, agent — all min_version 11.9.0). Every other doctype passes on 10.24 and 11.6.8 untouched, which is a useful incidental result: the documentation carry itself holds across all three supported majors. Co-Authored-By: Claude Opus 5 --- .../fix-issue/findings/mdl-executor.jsonl | 1 + mdl/executor/documentation_preserved_test.go | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/.claude/skills/fix-issue/findings/mdl-executor.jsonl b/.claude/skills/fix-issue/findings/mdl-executor.jsonl index 34eb9618fa..3ab7bf78b7 100644 --- a/.claude/skills/fix-issue/findings/mdl-executor.jsonl +++ b/.claude/skills/fix-issue/findings/mdl-executor.jsonl @@ -510,3 +510,4 @@ {"area": "mdl/executor", "date": "2026-09-04", "symptom": "`return` inside a `loop` passes `mxcli check` AND `exec`, then mxbuild fails **CE0068** \"End events cannot be placed inside a loop.\" \u2014 even though MDL062 exists precisely to catch that. It fires only when the microflow header has NO `returns T as $Var` clause.", "cause": "MDL062 stood down for the whole microflow whenever the AS clause was present, on two claims: that buildFlowGraph synthesizes the End event from the variable so none lands in the loop, and that the shape builds CE0109 instead. `describe microflow` shows the in-loop `return` written either way, so the first was never true. The second was a measurement artefact: mxbuild reports ONE error per microflow, and in the shape that was measured the AS variable was never assigned, so CE0109 \"Undefined variable\" won the race and hid CE0068 underneath. Adding `declare $Done Boolean = false` and changing nothing else turns the same microflow from CE0109 into CE0068. The exemption was deleted, not narrowed.", "file": "`mdl/executor/validate_microflow_ce_gaps.go` (checkReturnInLoop \u2014 the `v.returnType.Variable != \"\"` early return); test inverted in `validate_microflow_ce_gaps_test.go` (TestMDL062_ExemptsReturnsAsClause -> TestMDL062_FiresWithReturnsAsClause); examples `mdl-examples/bug-tests/captrack-19-return-in-loop-as-clause{,.fail}.mdl`", "insight": "A second error in the same document can HIDE the one you are measuring, because mxbuild reports one error per microflow. An exemption justified by \"measured: builds X instead\" is only sound if the reproduction was otherwise valid \u2014 here the repro was broken in a second way, and the error that surfaced was the one nobody was asking about. When a measurement says a construct is clean, add the minimum that removes every OTHER error from that document and measure again; the differential (CE0109 -> CE0068 on one added `declare`) is what settles it. Reported as CapTrackV2 FINDINGS \u00a719."} {"area": "mdl/executor", "date": "2026-09-04", "symptom": "`dataview dv (\u2026, OnClick: SHOW_PAGE \u2026)` parses, `mxcli check` is clean, `exec` writes the page without a word \u2014 and the rendered element has no handler and no role=\"button\". The same silence on `dynamictext`, `listview` and every other widget except a container or a button.", "cause": "`OnClick:` is an ALIAS for `Action:` (both stored as Properties[\"Action\"], #603), and mxcli writes that property for three widget kinds only: container/customcontainer, the buttons, and a navigationlist item. Every other widget drops it, and the property allow-lists behind MDL-WIDGET01/07 could not see that because they are widget-type AGNOSTIC \u2014 the same blind spot as #928's `editable:`. Added MDL-WIDGET23 (warning), with two messages: Mendix models no click action at all (dataview, dynamictext, inputs, groupbox\u2026) vs Mendix models one that mxcli cannot write (listview, staticimage, dynamicimage \u2014 measured against generated/metamodel).", "file": "`mdl/executor/validate_widget_onclick.go` (new), wired in `validate_widgets.go` beside validateWidgetEditability; `.claude/skills/mendix/create-page/reference/widgets.md`; example `mdl-examples/bug-tests/captrack-21-dataview-onclick-dropped.mdl`", "insight": "The first draft reported everything OUTSIDE an allow-list of the three writers, and running it over the shipped examples flagged three of them. Cause: `mxcli check` without `-p` has no widget registry, so `lookupWidgetDef` returns nil for a PLUGGABLE widget too and the caller's \"static widgets only\" branch silently does not hold \u2014 `datagrid` is DataGrid 2, a pluggable widget whose onClick the engine does write. For any rule keyed on widget type, an allow-list makes the unknown case an ERROR and a deny-list makes it silence; pick the deny-list, because a missed warning costs nothing and a false one tells an author their working page is broken. Running a new rule across mdl-examples/ before wiring it up is what caught it \u2014 the same exercise #893 describes. Reported as CapTrackV2 FINDINGS \u00a721."} {"area": "mdl/executor", "date": "2026-09-04", "symptom": "`ALTER SETTINGS MODEL AfterStartupMicroflow = 'Mod.MF_Seed'` accepts a microflow with no return type, `mxcli check` passes, and the build fails **CE0142** \"After startup microflow should return a boolean\". `ALTER MICROFLOW \u2026 RETURNS \u2026` does not parse either, so the remedy is DROP + CREATE.", "cause": "#274 made ALTER SETTINGS resolve the qualified names it writes, which catches a MISSPELLED microflow. Here the name resolves perfectly \u2014 the constraint is on the thing the setting names, not on the reference, and nothing looked at the return type. Added MDL073: a project-less pass (ValidateAfterStartupReturnType) for a microflow the script itself creates, which is the usual shape, plus the stored return type on the project path. Both call one function so they cannot drift. flowSignature gained ReturnKind because its existing Returns field is the entity name and cannot tell Boolean from void.", "file": "`mdl/executor/validate_settings_refs.go` (checkAfterStartupReturnsBoolean + ValidateAfterStartupReturnType), `validate_program.go`, `helpers.go` (buildMicroflowReturnTypes), `validate_datasource_args.go` (flowSignature.ReturnKind); examples `mdl-examples/bug-tests/captrack-6-after-startup-must-return-boolean{,.fail}.mdl`", "insight": "\"The reference resolves\" and \"the reference is usable\" are different questions, and a resolver answers only the first. Whenever a setting stores a NAME, ask what the platform requires of the named thing \u2014 an existence check will pass and the build will still fail, and the error arrives with no connection to the statement that caused it. The scope discipline that goes with it: only AfterStartup is type-checked, because only its rule was measured; BeforeShutdown and HealthCheck are left alone rather than constrained on a guess, and there is a control test asserting that. Reported as CapTrackV2 FINDINGS \u00a76."} +{"area": "mdl/executor", "date": "2026-09-04", "symptom": "Nightly integration tests fail on older Mendix versions only (10.24 and 11.6.8 red, 11.12/11.13/11.14 green): `--- FAIL: TestDocumentation_SurvivesRewrite/ai_model … create: create model requires Mendix 11.9.0+`", "cause": "The #1018 documentation-carry fixture table covers all 29 rewrite-capable doctypes, four of which (ai model, knowledge base, consumed mcp service, agent) do not exist below Mendix 11.9. The cases had no version gate, so on an older project they failed at CREATE — which says nothing about documentation carry, because the doctype is simply absent", "file": "`mdl/executor/documentation_preserved_test.go` (docPreserveCase.minMajor/minMinor + requireMinVersion in the runner)", "insight": "A doctype fixture table is a version-compatibility surface, not just coverage: adding a row for a gated doctype silently commits you to every version in the nightly matrix. Develop-version bias is what hides it — written against 11.13, all 29 pass, and only the matrix disagrees. Reproduce locally instead of reasoning: `MX_BINARY=~/.mxcli/mxbuild/11.6.0/modeler/mx go test -tags integration` gives the identical failure in seconds, and the cached mxbuild versions under ~/.mxcli/mxbuild/ are usually enough to cover the matrix. Two controls are needed, not one — that the gate SKIPS below the minimum, and that it is INERT above it. A gate that always skips turns the matrix green while testing nothing, which is the worse failure and the one nobody notices", "refs": ["ako/mxcli#1018", "mendixlabs/mxcli actions run 33846778362"], "ce": []} diff --git a/mdl/executor/documentation_preserved_test.go b/mdl/executor/documentation_preserved_test.go index da5fab8db9..d519ed5c62 100644 --- a/mdl/executor/documentation_preserved_test.go +++ b/mdl/executor/documentation_preserved_test.go @@ -39,6 +39,12 @@ type docPreserveCase struct { // anything else modelsdk-only). The harness defaults to legacy, so without // this the case fails at its own precondition and says nothing about #1018. modelsdk bool + // minMajor/minMinor gate a doctype that does not exist in every supported + // Mendix version. Without this the case fails at CREATE on an older + // project, which says nothing about #1018 — the doctype is simply absent. + // Kept as a version rather than a boolean so the reason is legible at the + // case, and mirrors sdk/versions/mendix-11.yaml. + minMajor, minMinor int // create carries a doc comment; rewrite deliberately does not. create string rewrite string @@ -231,24 +237,32 @@ func docPreserveCases() []docPreserveCase { }, { name: "ai model", + minMajor: 11, + minMinor: 9, storedOnly: true, create: doc + "create model TestModule.DocModel ( Provider: MxCloudGenAI );", rewrite: "create or modify model TestModule.DocModel ( Provider: MxCloudGenAI );", }, { name: "knowledge base", + minMajor: 11, + minMinor: 9, storedOnly: true, create: doc + "create knowledge base TestModule.DocKb ( Provider: MxCloudGenAI );", rewrite: "create or modify knowledge base TestModule.DocKb ( Provider: MxCloudGenAI );", }, { name: "consumed mcp service", + minMajor: 11, + minMinor: 9, storedOnly: true, create: doc + "create consumed mcp service TestModule.DocMcp ( ProtocolVersion: 'v2025_03_26' );", rewrite: "create or modify consumed mcp service TestModule.DocMcp ( ProtocolVersion: 'v2025_03_26' );", }, { name: "agent", + minMajor: 11, + minMinor: 9, storedOnly: true, create: "create model TestModule.DocAgentModel ( Provider: MxCloudGenAI );\n" + doc + "create agent TestModule.DocAgent ( UsageType: Task, Model: TestModule.DocAgentModel, SystemPrompt: 'p' );", @@ -273,6 +287,10 @@ func TestDocumentation_SurvivesRewrite(t *testing.T) { } defer env.teardown() + if tc.minMajor > 0 { + env.requireMinVersion(t, tc.minMajor, tc.minMinor) + } + if err := env.executeMDL(tc.create); err != nil { t.Fatalf("create: %v", err) }