From 85da9d6ff0b12a6563527d9e441940ac5257e339 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 23 Sep 2026 11:48:47 +0000 Subject: [PATCH 01/15] fix(page-fidelity): credit ALTER PAGE image bindings, skip CUT rows in bindings mxcli DESCRIBE prints IMAGE ImageUrl as a bare '{1}' and drops the bound attribute, so a DESCRIBE-only score reported bound images as missed. The scorer now reads ALTER PAGE bodies too and notes the DESCRIBE blind spot. Regression fixture from real marketplace-rnd DESCRIBE output (renamed). Field run: marketplace-rnd CatalogView_v5, 78% -> 82% with script 25. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01DyrzWc5YejD1iBLgJ7TS7q --- CHANGELOG.md | 1 + project-bin/page-fidelity.js | 45 ++++++++++++++++--- .../fixtures/page-fidelity-mocks/CAPTURE.md | 21 +++++++++ .../bind-contract-alter.mdl | 6 +++ .../bind-contract-describe.mdl | 31 +++++++++++++ .../page-fidelity-mocks/bind-contract.html | 34 ++++++++++++++ tests/wave2/test-page-fidelity-mocks.sh | 29 ++++++++++++ 7 files changed, 160 insertions(+), 7 deletions(-) create mode 100644 tests/wave2/fixtures/page-fidelity-mocks/bind-contract-alter.mdl create mode 100644 tests/wave2/fixtures/page-fidelity-mocks/bind-contract-describe.mdl create mode 100644 tests/wave2/fixtures/page-fidelity-mocks/bind-contract.html diff --git a/CHANGELOG.md b/CHANGELOG.md index 51ab0539..797ee22c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ three commits past it), and a bug report can name a release instead of a sha nob Sections dated before 2026-09-19 predate the cycle and stay as they are. ## Unreleased +- fix(project-bin/page-fidelity.js): **two sources of false binding misses, found re-scoring CatalogView_v5.** (1) mxcli DESCRIBE (v0.23.0) prints an IMAGE widget's `ImageUrl` as a bare `'{1}'` and drops the attribute parameters the model holds, so an image bound by `alter page … set ImageUrl = [Attr]` scored as missed — indistinguishable from the unbound page, so the scorer does not guess: it now reads `ALTER PAGE` bodies from any extra input (`… - binding-script.mdl < describe.mdl`, logged as source `describe+script`), accepts `Class =`/`DynamicClasses =` from them, and prints a note naming the blind spot when an IMAGE-row binding misses over bare `'{n}'` templates. (2) `bindRows` scored struck-through / CUT / NOT BUILDABLE bind-table rows that the contract reader already skipped — both now share one `notOwed()` test. Measured on marketplace-rnd CatalogView_v5: bindings 13/18 -> 14/16, fidelity 78% -> 82%; Skill_Details and UserGroupsAdmin_Overview unchanged (86%, 95%). The project copy's contract dimension (not in this copy) also stopped harvesting `css` from "ds.css" as an owed class. Pinned in `tests/wave2/test-page-fidelity-mocks.sh` (+ `bind-contract*` fixtures) — marketplace-rnd - docs(pipeline-walks): **`docs/pipeline-walks.html` — a process diagram per entry mode, with the scripts run at every step.** Shared spine, migration, requirements-driven (incl. the docs-ready fast path), greenfield, change-an-existing-app (opening with the app-mapping step: `SHOW STRUCTURE`, `graph-report`, `lint`, `report`, security matrix, `marketplace diff`), à-la-carte tracks A/A2/B, and the Stage 5 BUILD→GATE→PROVE→LOOK→CONFIRM loop, each as a mermaid flowchart plus a stage/what/scripts table. Linked from the README entry-modes paragraph — Maurits Visser - fix(bin/doctor.sh): **doctor told every Podman user "docker is not installed"** — the section advertised Podman in its advice text ("Rancher Desktop or Podman … are common substitutes") while all four probes ran `docker` only: `docker info`, the `command -v docker` gate, the not-installed warning, and a start hint that said `open -a Docker`. So a machine fully able to run the container lane on Podman, but without the docker shim, was reported broken — and on a team that cannot licence Docker Desktop that reads as "go install software you are not allowed to have" (a colleague's machine-ready status carried "Docker not installed" as a known issue; they may have had Podman all along). Detection is now docker-then-podman (`MXTK_CONTAINER_RUNTIME` forces one), the runtime is **named** in the report (`podman responding — …`), the start hint knows `podman machine start` / `podman.socket`, and the not-installed warning names Podman as the licence-free option instead of implying Docker Desktop is required. Same bounded background/poll/kill probe for both, same 0/1/2 exit contract; `mxcli docker check` invocation deliberately untouched (different repo). **Not field-run** — no container runtime in the authoring container; needs one run on a Mac with Podman and no `docker` on PATH. Driver: the Mendix migration team's Docker Desktop licensing constraint — Maurits Visser - learn(skills/doctor-triage.md): **"doctor.sh says red — what now?" is now on disk instead of in a Slack thread.** Three failures that render identically get separated: my environment is wrong / the toolkit's own self-check is wrong / this line does not apply in my lane. Check the machine before naming a fix — a wrong-arch binary, a missing one and a broken self-check all read the same, which is how *"install Studio Pro 10.24.18"* became the first confident answer to a Linux-ELF mxbuild on a Mac, and how a false `fail (unreadable error file)` (the self-test bug fixed in `c0ea53c`) sent people to audit their own machines. Also: the three toolchain lanes (bundled `mx` — macOS ships one only from Mendix 11 — the Linux-only CDN toolchain, and the container lane, runtime-agnostic although doctor still probes only `docker`), a derivation for which FAILs block rather than a list that rots, the N+M-lines-dispositioned bound, and the VM/arch caveat. From the macOS onboarding thread of 2026-09-22 — Yvann, and the four people in it diff --git a/project-bin/page-fidelity.js b/project-bin/page-fidelity.js index e0a6f259..be08ca11 100755 --- a/project-bin/page-fidelity.js +++ b/project-bin/page-fidelity.js @@ -265,19 +265,30 @@ function localMockClasses(html) { // page. One row per binding; a row scores when every identifier-looking token // in its datasource cell (CamelCase words, Entity.Attr paths) appears in the // page MDL. Rows whose cell names no identifier are annotation prose and skip. +// +// A row the author struck through () or whose fifth (Verdict) cell says CUT / NOT +// BUILDABLE is not owed. bindRows used to skip only header rows, so a wireframe's +// `Demo chip (curated tiles)` row (verdict "CUT — would be empty on 15 of the 16 +// tiles") was scored as a missed binding on a page that correctly left it out +// (marketplace-rnd CatalogView_v5, 2026-09-23). +const notOwed = trInner => { + if (//i.test(trInner)) return true; + const raw = [...trInner.matchAll(/]*>([\s\S]*?)<\/t[dh]>/gi)].map(c => c[1]); + return raw.length >= 5 && /NOT BUILDABLE|\bCUT\b|NOT from the CLI/i.test(strip(raw[4])); +}; function bindRows(html) { const t = innerBalanced(html, /<(table)[^>]*class="[^"]*\b(?:bind|bt)\b[^"]*"/i); if (!t) return []; const rows = []; for (const tr of t.matchAll(/]*>([\s\S]*?)<\/tr>/gi)) { const cells = [...tr[1].matchAll(/]*>([\s\S]*?)<\/t[dh]>/gi)].map(c => strip(c[1])); - if (!cells.length || / ids.add(x)); for (const m of src.matchAll(/\b([A-Z][a-z0-9]+(?:[A-Z][A-Za-z0-9]*)+)\b/g)) ids.add(m[1]); - if (ids.size) rows.push({ label: (cells[0] || '?').slice(0, 40), ids: [...ids] }); + if (ids.size) rows.push({ label: (cells[0] || '?').slice(0, 40), ids: [...ids], image: /\bIMAGE\b/.test(cells[1] || '') }); } return rows; } @@ -356,8 +367,19 @@ function wfFacts(html) { // identically in two modules across the MDLS list keeps the first module seen. let MODULE = null; +// ALTER PAGE bodies for the page are read too, after the declaration(s). mxcli DESCRIBE +// (v0.23.0) renders an IMAGE widget's ImageUrl as its bare template — `ImageUrl: '{1}'` — +// and DROPS the template's attribute parameters, although the model holds them +// (Forms$ClientTemplate.Parameters → DomainModels$AttributeRef, verified in the .mpr BSON). +// Measured on marketplace-rnd CatalogView_v5, 2026-09-23: a script bound both card images +// with `set ImageUrl = [Attr] on `, the app shows the logos, and DESCRIBE still +// printed `'{1}'` — indistinguishable from the unbound pre-fix page, so the scorer reported +// two bindings missed. Guessing from `'{1}'` would credit exactly the unbound defect that +// script fixed, so it does not; instead the binding script is passed as another input +// (`… - path/to/alter.mdl < describe.mdl`) and its ALTER body counts. An ALTER-only input +// is not a page: without a CREATE the run still exits 2. function pageMdl() { - let out = ''; + let out = '', alters = ''; for (const f of MDLS) { const src = f === '-' ? fs.readFileSync(0, 'utf8') : fs.readFileSync(f, 'utf8'); const re = new RegExp( @@ -366,8 +388,10 @@ function pageMdl() { if (!MODULE) MODULE = m[3]; out += pageBody(src, m.index) + '\n'; } + const alt = new RegExp('ALTER\\s+PAGE\\s+"?[A-Za-z0-9_]+"?\\."?' + PAGE + '"?\\b', 'gi'); + for (const m of src.matchAll(alt)) alters += pageBody(src, m.index) + '\n'; } - return out; + return out && out + alters; } // Known limit: the counter does not skip quoted strings, so an UNBALANCED brace inside a @@ -404,10 +428,11 @@ function pageBody(src, from) { function score(wf, mdl) { const corpus = norm(mdl); const cls = new Set(); - for (const m of mdl.matchAll(/Class:\s*['"]([^'"]+)['"]/gi)) m[1].split(/\s+/).forEach(c => cls.add(c)); + // `Class:` in a declaration, `Class =` in an ALTER PAGE SET. + for (const m of mdl.matchAll(/\bClass\s*[:=]\s*['"]([^'"]+)['"]/gi)) m[1].split(/\s+/).forEach(c => cls.add(c)); // DynamicClasses expressions emit class names conditionally — every quoted // token that looks like a class name lands in the DOM on some branch. - for (const m of mdl.matchAll(/DynamicClasses:\s*'((?:[^']|'')*)'/gi)) + for (const m of mdl.matchAll(/DynamicClasses\s*[:=]\s*'((?:[^']|'')*)'/gi)) for (const t of m[1].matchAll(/''([a-z][a-z0-9-]*)''|'([a-z][a-z0-9-]*)'/gi)) cls.add(t[1] || t[2]); const hit = txt => { const w = words(txt); return w.length ? w.filter(x => corpus.includes(x)).length / w.length >= 0.6 : true; }; @@ -452,6 +477,12 @@ const miss = [...s.h.miss.map(x => 'heading: ' + x), ...s.b.miss.map(x => 'actio ...(s.c.miss.length ? ['classes: ' + s.c.miss.join(' ')] : []), ...s.bd.miss.map(r => 'binding: ' + r.label + ' (' + r.ids.join(' ') + ')')]; if (miss.length) console.log(' missed:\n ' + miss.join('\n ')); +// Say so when a binding miss may be DESCRIBE's blind spot rather than the page's (see pageMdl). +const bareImg = (mdl.match(/ImageUrl:\s*'\{\d+\}'/g) || []).length; +if (bareImg && s.bd.miss.some(r => r.image)) + console.log(' note: ' + bareImg + ' image widget(s) show ImageUrl as a bare \'{n}\' template — DESCRIBE drops' + + ' ImageUrl parameters, so their attribute bindings cannot be seen here; pass the script that' + + ' set them (ALTER PAGE … set ImageUrl = [Attr]) as another input'); if (s.c.chrome.length) console.log(' chrome (layout-supplied, not scored): ' + s.c.chrome.join(' ')); if (wf.mockUsed.length) console.log(' bound-data mocks (wireframe-local, text not scored): ' + wf.mockUsed.join(' ')); if (wf.structural.length) console.log(' wireframe structure (kept as page content, not a mock): ' + wf.structural.join(' ')); @@ -533,7 +564,7 @@ if (!NOLOG) { new Date().toISOString().slice(0, 16).replace('T', ' '), PAGE, MODULE || '-', s.pct === null ? '-' : s.pct + '%', frac(s.h), frac(s.b), frac(s.k), frac(s.c), frac(s.bd), - STUB ? 'stub' : MDLS[0] === '-' ? 'describe' : 'draft', + STUB ? 'stub' : MDLS.includes('-') ? (MDLS.length > 1 ? 'describe+script' : 'describe') : 'draft', path.relative(root, path.resolve(WF_FILE)) + (WF_REF ? '#/' + WF_REF.route : ''), ].join('\t'); fs.appendFileSync(tsv, row + '\n'); diff --git a/tests/wave2/fixtures/page-fidelity-mocks/CAPTURE.md b/tests/wave2/fixtures/page-fidelity-mocks/CAPTURE.md index aa361951..5bc0cb82 100644 --- a/tests/wave2/fixtures/page-fidelity-mocks/CAPTURE.md +++ b/tests/wave2/fixtures/page-fidelity-mocks/CAPTURE.md @@ -46,3 +46,24 @@ which screen carries it. The old rule was `uses === 1 && kept < 0.4` -> structure. Only `ds-card`-sized wrappers cleared it; the page header did not, and it is the one holding the heading. + +## bind-contract — the five-column bind table and DESCRIBE's blind ImageUrl (2026-09-23) + +`bind-contract.html` carries five rows of the real bind table in marketplace-rnd's +`CatalogView-redesign-v2.html`, **verbatim in markup and prose** — including the struck +`Demo chip` row with its `CUT —` verdict, the CSS cell "(already a ds.css candidate)", +and the Dark-mode cell naming `#mxapp.theme-dark`. Changed: the class prefix (`mps-` -> `x-`) +and the attribute names (`LastPublishedVersionLogo` -> `LogoUrl`, +`PublisherOrganizationLogo` -> `PublisherLogoUrl`, `LastPublishedVersionDemoUrl` -> `DemoUrl`). + +`bind-contract-describe.mdl` is a reduction of `mxcli describe page` (v0.23.0) output for the +built page: the two `image` widgets are verbatim apart from names, and they show the fact +nobody would have imagined — `ImageUrl: '{1}'` with **no parameters**, although the `.mpr` +holds a `Forms$ClientTemplate` whose `Parameters` carry the attribute (read from the unit's +BSON). The surrounding widgets were cut to what the rows need. + +`bind-contract-alter.mdl` is the binding script's shape (`set ImageUrl = [Attr] on `), +which is what makes those bindings visible to the scorer. + +Measured on the real page: bindings 13/18 -> 14/16 (DemoUrl row no longer owed; the two +image rows seen through the ALTER script), contract 16/30 -> 16/29 (`.css` gone). diff --git a/tests/wave2/fixtures/page-fidelity-mocks/bind-contract-alter.mdl b/tests/wave2/fixtures/page-fidelity-mocks/bind-contract-alter.mdl new file mode 100644 index 00000000..9f908f53 --- /dev/null +++ b/tests/wave2/fixtures/page-fidelity-mocks/bind-contract-alter.mdl @@ -0,0 +1,6 @@ +-- The binding script: mxcli's IMAGE widget takes ImageUrl as a direct attribute-path array. +alter page Demo.Catalog { + set ImageUrl = [LogoUrl] on imgLogo + set ImageUrl = [PublisherLogoUrl] on imgPublisherTile +} +/ diff --git a/tests/wave2/fixtures/page-fidelity-mocks/bind-contract-describe.mdl b/tests/wave2/fixtures/page-fidelity-mocks/bind-contract-describe.mdl new file mode 100644 index 00000000..f178764b --- /dev/null +++ b/tests/wave2/fixtures/page-fidelity-mocks/bind-contract-describe.mdl @@ -0,0 +1,31 @@ +create or modify page Demo.Catalog ( + Title: 'Catalog', + Layout: Demo.Layout_Main, + Class: 'page-dashboard' +) { + container ctnDeck (Class: 'x-deck') { + dynamictext txtH1 (Content: 'Catalog', RenderMode: H1) + } + snippetcall sncFilterRail (Snippet: Demo.Snip_FilterRail) + listview lvCards ( + DataSource: microflow Demo.DS_LoadItems, + PageSize: 1000 + ) { + -- Context: $currentObject (Demo.Item), $lvCards (selection) + container ctnCanvas (Class: 'x-canvas') { + image imgLogo ( + ImageType: imageUrl, + ImageUrl: '{1}', + WidthUnit: percentage, + Class: 'x-canvas__img' + ) + } + image imgPublisherTile ( + ImageType: imageUrl, + ImageUrl: '{1}', + WidthUnit: pixels, + Width: 40, + Class: 'x-ptile' + ) + } +} diff --git a/tests/wave2/fixtures/page-fidelity-mocks/bind-contract.html b/tests/wave2/fixtures/page-fidelity-mocks/bind-contract.html new file mode 100644 index 00000000..bab6620f --- /dev/null +++ b/tests/wave2/fixtures/page-fidelity-mocks/bind-contract.html @@ -0,0 +1,34 @@ + +Catalog — bind contract + + +
+

Catalog

+
filters
+
+ + + + + + + + + + + + + + + + +
ComponentWidgetBinding / datasourceCSSVerdict
Filter rail (sticky, left)SNIPPETCALL, existing snippet, in x-l-left-main__leftexisting.x-filter-rail (already a ds.css candidate)THEME
16:9 image canvasIMAGE ImageType: imageUrl in a CONTAINER$Item/LogoUrl — 4,829 / 5,174 (93.3%).x-canvas, object-fit:cover, ~14 linesTHEME
Publisher tile (overlapping)IMAGE in a CONTAINER$Item/PublisherLogoUrl — 4,823 / 5,174 (93.2%).x-ptile, absolute, ~8 linesTHEME
Demo chip (curated tiles)LINKBUTTONDemoUrl is 64.7% corpus-wide but + 1 / 16 on Platform rows—CUT — would be empty on 15 of the 16 tiles it exists for
Dark modeClass: 'theme-dark' on the page container—≈12 lines. The app already defines 419 dark tokens on + #mxapp.theme-darkTHEME (small)
+ diff --git a/tests/wave2/test-page-fidelity-mocks.sh b/tests/wave2/test-page-fidelity-mocks.sh index 55b30027..7280473e 100755 --- a/tests/wave2/test-page-fidelity-mocks.sh +++ b/tests/wave2/test-page-fidelity-mocks.sh @@ -110,6 +110,35 @@ has "the repeated list-item class is a mock" "$MOCKLINE2" "x-item" hasnt "the once-used card wrapper is not a mock" "$MOCKLINE2" "x-card" hasnt "a mocked list page does not score null" "$OUT2" "fidelity null%" +echo " -- bind table: struck/CUT rows are not owed; DESCRIBE's blind ImageUrl; ALTER bodies count" +# bind-contract.html reproduces rows of a real five-column bind table (marketplace-rnd +# CatalogView-redesign-v2.html, 2026-09-23); bind-contract-describe.mdl is the shape mxcli +# v0.23.0 DESCRIBE printed for the built page — two IMAGE widgets whose ImageUrl renders as a +# bare '{1}' although the model binds an attribute. See CAPTURE.md. +BD="$FIX/bind-contract-describe.mdl" +OUT3=$(cd "$TMP" && node "$SUT" --no-log "$FIX/bind-contract.html" Catalog - < "$BD" 2>&1) +echo "$OUT3" | sed 's/^/ | /' +hasnt "a struck-through CUT row is not a missed binding" "$OUT3" "DemoUrl" +has "the CUT row leaves the binding denominator" "$OUT3" "bindings 0/2" +has "DESCRIBE alone cannot see the image bindings" "$OUT3" "(LogoUrl)" +has "and the scorer says why" "$OUT3" "DESCRIBE drops ImageUrl parameters" +OUT4=$(cd "$TMP" && node "$SUT" --no-log "$FIX/bind-contract.html" Catalog - "$FIX/bind-contract-alter.mdl" < "$BD" 2>&1) +echo "$OUT4" | sed 's/^/ | /' +has "the ALTER script's ImageUrl bindings count" "$OUT4" "bindings 2/2" +hasnt "no DESCRIBE note once the bindings are seen" "$OUT4" "DESCRIBE drops" +OUT5=$(cd "$TMP" && node "$SUT" --no-log "$FIX/bind-contract.html" Catalog "$FIX/bind-contract-alter.mdl" 2>&1; echo "exit=$?") +has "an ALTER with no CREATE is not a page" "$OUT5" "exit=2" +if grep -q 'function contractRows' "$SUT"; then + # The contract dimension reads the CSS column. "(already a ds.css candidate)" once + # harvested `css` as an owed class; "#mxapp.theme-dark" is a compound selector whose class + # IS owed — the fix must drop the first and keep the second. + hasnt "a file name in the CSS cell is not a class" "$OUT3" "(.css)" + has "a compound selector's class is still owed" "$OUT3" "(.theme-dark)" + has "contract counts 4 classes, not 5" "$OUT3" "contract 2/4" +else + echo " skip contract assertions: this page-fidelity.js has no contract dimension" +fi + echo printf 'test-page-fidelity-mocks: %d passed, %d failed\n' "$PASS" "$FAIL" [ "$FAIL" -eq 0 ] From c326d13bd3c9c25bb4796df8d7a9f2f102276427 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 26 Sep 2026 19:11:10 +0000 Subject: [PATCH 02/15] Bug ledger: MPR012 flags legacy dynamic images as React-only errors on a Dojo-client Mendix 11 project Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01DyrzWc5YejD1iBLgJ7TS7q --- CHANGELOG.md | 1 + bug-logs/mxcli-bugs.md | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f0bb0fe..5985f57d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ three commits past it), and a bug report can name a release instead of a sha nob Sections dated before 2026-09-19 predate the cycle and stay as they are. ## Unreleased +- learn(bug-logs): **`BUG-DRAFT-mpr012-assumes-react-client`**: in v0.23.0, lint MPR012 reports every legacy dynamic image as a React-only CE0582 error. On a Mendix 11.12.2 model that still builds for the Dojo client, `mx check` reports 0 errors for the same widgets. That added 57 findings to the ratchet with no model change. Marketplace-RnD guest-groups scan. - fix(project-bin/page-fidelity.js): **two sources of false binding misses, found re-scoring CatalogView_v5.** (1) mxcli DESCRIBE (v0.23.0) prints an IMAGE widget's `ImageUrl` as a bare `'{1}'` and drops the attribute parameters the model holds, so an image bound by `alter page … set ImageUrl = [Attr]` scored as missed — indistinguishable from the unbound page, so the scorer does not guess: it now reads `ALTER PAGE` bodies from any extra input (`… - binding-script.mdl < describe.mdl`, logged as source `describe+script`), accepts `Class =`/`DynamicClasses =` from them, and prints a note naming the blind spot when an IMAGE-row binding misses over bare `'{n}'` templates. (2) `bindRows` scored struck-through / CUT / NOT BUILDABLE bind-table rows that the contract reader already skipped — both now share one `notOwed()` test. Measured on marketplace-rnd CatalogView_v5: bindings 13/18 -> 14/16, fidelity 78% -> 82%; Skill_Details and UserGroupsAdmin_Overview unchanged (86%, 95%). The project copy's contract dimension (not in this copy) also stopped harvesting `css` from "ds.css" as an owed class. Pinned in `tests/wave2/test-page-fidelity-mocks.sh` (+ `bind-contract*` fixtures) — marketplace-rnd - learn(skills/learned-file-upload-widget.md): **a file upload mxcli can author, with proof that it uploads.** The Mendix File Uploader 2.5.0 bound to a `System.FileDocument` specialisation: the MDL shape (entities, grants, create/delete microflows, advanced formats), which upload widgets mxcli cannot author (classic FileManager, PDS uploader), the two traps with workarounds (a simple-mode `allowedfileformat` passes exec and fails `mx check` with CE0463; an uploader DESCRIBE will not re-exec, `exposes 2 datasources`), and the six-step upload instrument. Field run on stock v0.24.0: the section-4 MDL taken verbatim from the skill gave `mx check` 0 errors, 2/2 files stored, 2/2 downloads sha256-equal, `.csv` rejected with 0 rows; both traps reproduce unchanged on v0.24.0 — a Mendix app-rebuild project - new(skills/mendix-best-practices-index.md): **one row per Mendix best-practice area: the Mendix docs page, the bundled `assess-quality` section, the toolkit skill that applies it before the write, and the `mxcli lint` rule that catches it after exec.** An index, not a copy — the practice text stays on the Mendix pages (17 URLs verified HTTP 200 on 2026-09-25) and in the mxcli-bundled skill; Mendix's own Best Practice Recommender rules (MXP001–016) anchor the performance rows, and four rows say out loud that no lint rule exists and the preflight checklist is the only check. Routed `all` agents, stages 3/5/6, group reference — Maurits Visser diff --git a/bug-logs/mxcli-bugs.md b/bug-logs/mxcli-bugs.md index 7962dde1..bc7abd3f 100644 --- a/bug-logs/mxcli-bugs.md +++ b/bug-logs/mxcli-bugs.md @@ -5758,3 +5758,22 @@ correct script that trips MPR008 through this defect looks identical to a real o gate-agent's "do not accept the rise with `--update-baseline`" rule needs this entry to tell the two apart: an MPR008 whose two elements are a merge and the activity after a loop in an `if` branch is this bug, and the fix is the workaround above, not a baseline bump. + +## BUG-DRAFT-mpr012-assumes-react-client: lint MPR012 reports every legacy dynamic image as a React-client error (CE0582) on a Mendix 11 project that still builds for the Dojo client (2026-09-26) + +> **NOT YET FILED.** + +**Discovered:** 2026-09-26, when an existing-app change project merged upstream `main`. The lint ratchet jumped by 57, and every one of the new findings was MPR012. +**Reproducible:** yes, on every run against that model. **mxcli version:** v0.23.0. **Mendix:** 11.12.2. + +**What happens.** MPR012 says: "dynamic image 'imageViewer1' in … is not supported by the React client (Mendix 10.7+, the only client on 11) — mxbuild reports CE0582". It fires on every `Forms$ImageViewer`: 57 of them on that project's own modules. Two things show this is not an error for this model: +- `mx check` (mxbuild 11.12.2) on the same `.mpr` reports **0 errors and no CE0582**. +- The project still builds for the **Dojo** client: the built `deployment/web/index.html` loads `mxui/mxui.js`. + +So the rule's premise, "the only client on 11", does not hold for this project. + +**Expected:** MPR012 should fire only when the project builds for the React client. Otherwise it should be an info-level "blocks a React migration" note, not a warning. + +**Workaround:** accept the rise with `--update-baseline` and name MPR012 in the commit message. Cite the clean `mx check` and the Dojo marker as evidence. Keep the list, because it is the to-do list for a future move to the React client. + +**Why it matters for the toolkit.** `exec.sh` runs the lint ratchet after every clean mxbuild. A new built-in rule that fires on untouched legacy widgets fails that ratchet on the first build after a toolkit or mxcli update, even though the model did not change. Before treating a sudden rise in one new rule as a regression, check it against `mx check`. From c84f69437ae9a5a186c60bb433a37782e3c63e11 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 26 Sep 2026 19:40:47 +0000 Subject: [PATCH 03/15] learn(bug-logs): partial revoke on association members is a silent no-op Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01DyrzWc5YejD1iBLgJ7TS7q --- CHANGELOG.md | 1 + bug-logs/mxcli-bugs.md | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5985f57d..93ef9d44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ three commits past it), and a bug report can name a release instead of a sha nob Sections dated before 2026-09-19 predate the cycle and stay as they are. ## Unreleased +- learn(bug-logs): **`BUG-DRAFT-partial-revoke-association-noop`**: in v0.23.0, `revoke R on E (write ())` reports success and leaves the association `ReadWrite`. The same statement on an attribute works. Workaround: revoke the whole role, re-grant from `DESCRIBE ENTITY` output, and verify with `SHOW ACCESS`. From the Marketplace-RnD guest-groups scan. - learn(bug-logs): **`BUG-DRAFT-mpr012-assumes-react-client`**: in v0.23.0, lint MPR012 reports every legacy dynamic image as a React-only CE0582 error. On a Mendix 11.12.2 model that still builds for the Dojo client, `mx check` reports 0 errors for the same widgets. That added 57 findings to the ratchet with no model change. Marketplace-RnD guest-groups scan. - fix(project-bin/page-fidelity.js): **two sources of false binding misses, found re-scoring CatalogView_v5.** (1) mxcli DESCRIBE (v0.23.0) prints an IMAGE widget's `ImageUrl` as a bare `'{1}'` and drops the attribute parameters the model holds, so an image bound by `alter page … set ImageUrl = [Attr]` scored as missed — indistinguishable from the unbound page, so the scorer does not guess: it now reads `ALTER PAGE` bodies from any extra input (`… - binding-script.mdl < describe.mdl`, logged as source `describe+script`), accepts `Class =`/`DynamicClasses =` from them, and prints a note naming the blind spot when an IMAGE-row binding misses over bare `'{n}'` templates. (2) `bindRows` scored struck-through / CUT / NOT BUILDABLE bind-table rows that the contract reader already skipped — both now share one `notOwed()` test. Measured on marketplace-rnd CatalogView_v5: bindings 13/18 -> 14/16, fidelity 78% -> 82%; Skill_Details and UserGroupsAdmin_Overview unchanged (86%, 95%). The project copy's contract dimension (not in this copy) also stopped harvesting `css` from "ds.css" as an owed class. Pinned in `tests/wave2/test-page-fidelity-mocks.sh` (+ `bind-contract*` fixtures) — marketplace-rnd - learn(skills/learned-file-upload-widget.md): **a file upload mxcli can author, with proof that it uploads.** The Mendix File Uploader 2.5.0 bound to a `System.FileDocument` specialisation: the MDL shape (entities, grants, create/delete microflows, advanced formats), which upload widgets mxcli cannot author (classic FileManager, PDS uploader), the two traps with workarounds (a simple-mode `allowedfileformat` passes exec and fails `mx check` with CE0463; an uploader DESCRIBE will not re-exec, `exposes 2 datasources`), and the six-step upload instrument. Field run on stock v0.24.0: the section-4 MDL taken verbatim from the skill gave `mx check` 0 errors, 2/2 files stored, 2/2 downloads sha256-equal, `.csv` rejected with 0 rows; both traps reproduce unchanged on v0.24.0 — a Mendix app-rebuild project diff --git a/bug-logs/mxcli-bugs.md b/bug-logs/mxcli-bugs.md index bc7abd3f..c3ff7d8f 100644 --- a/bug-logs/mxcli-bugs.md +++ b/bug-logs/mxcli-bugs.md @@ -5777,3 +5777,24 @@ So the rule's premise, "the only client on 11", does not hold for this project. **Workaround:** accept the rise with `--update-baseline` and name MPR012 in the commit message. Cite the clean `mx check` and the Dojo marker as evidence. Keep the list, because it is the to-do list for a future move to the React client. **Why it matters for the toolkit.** `exec.sh` runs the lint ratchet after every clean mxbuild. A new built-in rule that fires on untouched legacy widgets fails that ratchet on the first build after a toolkit or mxcli update, even though the model did not change. Before treating a sudden rise in one new rule as a regression, check it against `mx check`. + +## BUG-DRAFT-partial-revoke-association-noop: `revoke R on E (write ())` reports success and changes nothing (2026-09-26) + +> **NOT YET FILED.** + +**Discovered:** 2026-09-26, on an existing-app change project, while making a component admin's guest links read-only. +**Reproducible:** yes, on a scratch copy of the model. **mxcli version:** v0.23.0. **Mendix:** 11.12.2. + +**What happens.** You revoke write on association members, for example `revoke Mod.Member on Mod.Guest (write ("Group_Guests", "Guest_OrganisationEmployee"));`. It parses, `exec` reports it as applied, and mxbuild stays clean. But `SHOW ACCESS ON ENTITY` still lists both associations as `ReadWrite`. The same statement with an attribute works: `(write (Email))` turns Email into `ReadOnly`. The qualified form `(write (Mod."Group_Guests"))` gives a parse error. + +**Expected:** association members are downgraded the same way attributes are. If they can't be, the statement should fail with an error. It should never be a silent no-op. + +**Workaround:** +1. Revoke the role from the entity entirely: `revoke R on E;`. This removes R from every rule of E. +2. Re-grant each of R's rules from its `DESCRIBE ENTITY` line, changing only the member lists. The XPath then stays byte-for-byte the same. +3. Diff the `where` clauses before and after. +4. Verify with `SHOW ACCESS`. + +Never take the exec's "applied" as proof. + +**Why it matters for the toolkit.** It is a false green on a security change (`skills/learned-detection-gaps.md` class): every gate passes and the right it was meant to remove is still there. From f134bcd8c33e4ee0fba7964d83f9df81036b4639 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 05:49:17 +0000 Subject: [PATCH 04/15] learn(bug-logs): check --references misses a queue created in the same script Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01DyrzWc5YejD1iBLgJ7TS7q --- CHANGELOG.md | 1 + bug-logs/mxcli-bugs.md | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93ef9d44..55019d49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ three commits past it), and a bug report can name a release instead of a sha nob Sections dated before 2026-09-19 predate the cycle and stay as they are. ## Unreleased +- learn(bug-logs): **`BUG-DRAFT-check-references-misses-same-script-queue`**: in v0.23.0, `check --references` does not see a task queue created earlier in the same script. Workaround: put the queue in its own script and run it first. From the Marketplace-RnD guest-groups scan. - learn(bug-logs): **`BUG-DRAFT-partial-revoke-association-noop`**: in v0.23.0, `revoke R on E (write ())` reports success and leaves the association `ReadWrite`. The same statement on an attribute works. Workaround: revoke the whole role, re-grant from `DESCRIBE ENTITY` output, and verify with `SHOW ACCESS`. From the Marketplace-RnD guest-groups scan. - learn(bug-logs): **`BUG-DRAFT-mpr012-assumes-react-client`**: in v0.23.0, lint MPR012 reports every legacy dynamic image as a React-only CE0582 error. On a Mendix 11.12.2 model that still builds for the Dojo client, `mx check` reports 0 errors for the same widgets. That added 57 findings to the ratchet with no model change. Marketplace-RnD guest-groups scan. - fix(project-bin/page-fidelity.js): **two sources of false binding misses, found re-scoring CatalogView_v5.** (1) mxcli DESCRIBE (v0.23.0) prints an IMAGE widget's `ImageUrl` as a bare `'{1}'` and drops the attribute parameters the model holds, so an image bound by `alter page … set ImageUrl = [Attr]` scored as missed — indistinguishable from the unbound page, so the scorer does not guess: it now reads `ALTER PAGE` bodies from any extra input (`… - binding-script.mdl < describe.mdl`, logged as source `describe+script`), accepts `Class =`/`DynamicClasses =` from them, and prints a note naming the blind spot when an IMAGE-row binding misses over bare `'{n}'` templates. (2) `bindRows` scored struck-through / CUT / NOT BUILDABLE bind-table rows that the contract reader already skipped — both now share one `notOwed()` test. Measured on marketplace-rnd CatalogView_v5: bindings 13/18 -> 14/16, fidelity 78% -> 82%; Skill_Details and UserGroupsAdmin_Overview unchanged (86%, 95%). The project copy's contract dimension (not in this copy) also stopped harvesting `css` from "ds.css" as an owed class. Pinned in `tests/wave2/test-page-fidelity-mocks.sh` (+ `bind-contract*` fixtures) — marketplace-rnd diff --git a/bug-logs/mxcli-bugs.md b/bug-logs/mxcli-bugs.md index c3ff7d8f..826f960b 100644 --- a/bug-logs/mxcli-bugs.md +++ b/bug-logs/mxcli-bugs.md @@ -5798,3 +5798,18 @@ So the rule's premise, "the only client on 11", does not hold for this project. Never take the exec's "applied" as proof. **Why it matters for the toolkit.** It is a false green on a security change (`skills/learned-detection-gaps.md` class): every gate passes and the right it was meant to remove is still there. + +## BUG-DRAFT-check-references-misses-same-script-queue: `mxcli check --references` reports a queue created earlier in the same script as "task queue not found" (2026-09-27) + +> **NOT YET FILED.** + +**Discovered:** 2026-09-27, on an existing-app change project, while moving a batch job onto a new task queue. +**Reproducible:** yes. **mxcli version:** v0.23.0. **Mendix:** 11.12.2. + +**What happens.** You have a script that runs `create or modify queue M.Q ...;` and later `call microflow M.X(...) in queue M.Q;`. `check --references` fails with `task queue not found: M.Q (referenced by in queue)`. The checker says "references to objects created within the script are skipped", but queues are not skipped. `exec` of the same script on a scratch copy works, and mxbuild is clean. + +**Expected:** a queue created earlier in the script counts as existing, the same way entities and microflows do. + +**Workaround:** put the `create queue` in its own script and exec it first. The second script then passes `check --references`. + +**Why it matters for the toolkit.** `exec.sh` refuses a script that fails check, so the single-script form never reaches the model. It costs one extra exec-and-gate cycle, about 5 minutes on a large model. From 1ae795f5652c1bc375f0ba1e8517a5c3692e70f4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 06:49:59 +0000 Subject: [PATCH 05/15] Inbox: create or modify association ignores owner change Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01DyrzWc5YejD1iBLgJ7TS7q --- .../2026-09-27-association-owner-ignored.md | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 contrib/inbox/2026-09-27-association-owner-ignored.md diff --git a/contrib/inbox/2026-09-27-association-owner-ignored.md b/contrib/inbox/2026-09-27-association-owner-ignored.md new file mode 100644 index 00000000..e7683bed --- /dev/null +++ b/contrib/inbox/2026-09-27-association-owner-ignored.md @@ -0,0 +1,25 @@ +# mxcli: `create or modify association` ignores an owner change + +**Source:** marketplace-rnd, guest-groups best-practices work (2026-09-27). mxcli v0.23.0, Mendix 11.12.2. +**Status:** unreviewed inbox drop. + +## Symptom +`create or modify association UserGroups.GuestGroup_App ... owner Both;` on an existing +association (owner Default) reports "Modified", yet `DESCRIBE ASSOCIATION` still shows owner +Default. The owner change is dropped without a message. + +## Why the obvious workaround is worse +`drop association` + `create association ... owner Both` does set the owner, but mints a new +association ID: existing DB links are lost, and every entity access rule's member right on that +association resets to None (GuestGroup's ReadWrite/ReadOnly rights were lost). On a cross-module +association into a module-owned entity it also gave CE0066. + +## Workaround used +A direct patch of the association unit in the MPR (SQLite `Unit` table, BSON `Contents`): flip +`Owner` to `Both` in place (same ID), append a `DomainModels$MemberAccess` (None) for the +association to every access rule of the other entity, recompute `ContentsHash` +(base64 sha256 of Contents). Proven on a scratch copy: mxbuild succeeded, `mx check` 0 errors. +Script: marketplace-rnd `mdlsource/guest-groups/14-one-to-one-guestgroup-app.py`. + +## Ask +Either apply the owner in `create or modify`, or refuse with an error that names the limitation. From 48e75340f07128ec57fbf41bfd09421f1ba30d4a Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 07:50:14 +0000 Subject: [PATCH 06/15] Inbox: marketplace-rnd guest-groups harvest (exec gate, v1 restore, upstream drafts) Hand-mined learnings from marketplace-rnd guest-groups work, triaged against the ledger, learned-* skills and existing inbox. Includes the exec.sh false-green on mxbuild version mismatch (29/29 rows), missing v1 auto-restore arm, XPath case CE0161, owner Both CE0066, and ranked mxcli upstream issue drafts. Probes ran on scratch copies only. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01DyrzWc5YejD1iBLgJ7TS7q --- ...026-09-27-association-owner-both-ce0066.md | 17 ++ ...7-audit-member-ce0066-and-mxcli-version.md | 15 ++ .../2026-09-27-boot-detect-login-patch.md | 13 ++ ...09-27-e2e-helpers-playwright-executable.md | 13 ++ .../2026-09-27-exec-autorestore-no-v1-arm.md | 15 ++ ...-exec-gate-false-green-version-mismatch.md | 26 +++ ...learned-microflow-patterns-stale-ce0639.md | 13 ++ .../2026-09-27-marketplace-rnd-patches.md | 166 ++++++++++++++++++ .../inbox/2026-09-27-mxcli-upstream-issues.md | 52 ++++++ ...-27-scratch-copy-mx-check-needs-folders.md | 15 ++ .../2026-09-27-story-contract-gate-gap.md | 12 ++ ...6-09-27-xpath-system-member-case-ce0161.md | 17 ++ 12 files changed, 374 insertions(+) create mode 100644 contrib/inbox/2026-09-27-association-owner-both-ce0066.md create mode 100644 contrib/inbox/2026-09-27-audit-member-ce0066-and-mxcli-version.md create mode 100644 contrib/inbox/2026-09-27-boot-detect-login-patch.md create mode 100644 contrib/inbox/2026-09-27-e2e-helpers-playwright-executable.md create mode 100644 contrib/inbox/2026-09-27-exec-autorestore-no-v1-arm.md create mode 100644 contrib/inbox/2026-09-27-exec-gate-false-green-version-mismatch.md create mode 100644 contrib/inbox/2026-09-27-learned-microflow-patterns-stale-ce0639.md create mode 100644 contrib/inbox/2026-09-27-marketplace-rnd-patches.md create mode 100644 contrib/inbox/2026-09-27-mxcli-upstream-issues.md create mode 100644 contrib/inbox/2026-09-27-scratch-copy-mx-check-needs-folders.md create mode 100644 contrib/inbox/2026-09-27-story-contract-gate-gap.md create mode 100644 contrib/inbox/2026-09-27-xpath-system-member-case-ce0161.md diff --git a/contrib/inbox/2026-09-27-association-owner-both-ce0066.md b/contrib/inbox/2026-09-27-association-owner-both-ce0066.md new file mode 100644 index 00000000..bf7a7e0c --- /dev/null +++ b/contrib/inbox/2026-09-27-association-owner-both-ce0066.md @@ -0,0 +1,17 @@ +# Addendum to association-owner-ignored: owner Both across modules → CE0066 + +**Source:** marketplace-rnd, scratch copy probe 2026-09-27, mxcli v0.23.0 / Mendix 11.12.2. +**Status:** unreviewed inbox drop; extends `2026-09-27-association-owner-ignored.md`. + +## Repro +`drop association` + `create association UserGroups.GuestGroup_App ... owner Both` where the +other end (`AppStore.App`) lives in another module. mxcli prints "Reconciled 3 access +rule(s)…" — only GuestGroup's rules. `AppStore.App`'s 13 rules get no MemberAccess for the new +association → `mx check`: `[CE0066] ... at Domain model of module 'AppStore'`. +Neither `update security` nor the audit-member strip script clears it. + +## So +There is no mxcli path to owner Both on a cross-module association today: `create or modify` +ignores it, drop+create leaves the far side stale. Only the raw BSON patch works +(`mdlsource/guest-groups/14-one-to-one-guestgroup-app.py`, proven on a scratch copy, 0 errors). +The project fell back to owner Default and a 1-* convention. diff --git a/contrib/inbox/2026-09-27-audit-member-ce0066-and-mxcli-version.md b/contrib/inbox/2026-09-27-audit-member-ce0066-and-mxcli-version.md new file mode 100644 index 00000000..5002a900 --- /dev/null +++ b/contrib/inbox/2026-09-27-audit-member-ce0066-and-mxcli-version.md @@ -0,0 +1,15 @@ +# Grants injecting System.owner/changedBy member access → CE0066 (fixed upstream; record the version) + +**Source:** marketplace-rnd probe `analysis/probes/2026-09-26-d13-ce0066/`. **Status:** unreviewed. + +## Failure +Grant scripts added MemberAccess entries for `System.owner` / `System.changedBy` on entities +without those audit members (AppCategory, AppContentType, InIDE.CatalogMenu) → CE0066. +Fixed upstream in mxcli 2455ee9f; **not reproduced on v0.23.0** (three probes, 0 errors). +The Mac exec almost certainly used an older mxcli on PATH (v0.21.0 seen there) instead of the +project's. + +## Ledger entry + process point +- Ledger: symptom, "fixed in 2455ee9f / ≥ v0.22", interim `strip-audit-member-access.py`. +- BUILD-LOG rows should record `mxcli --version` actually executed; exec.sh can stamp it. + A bug "reproduced" on the wrong binary costs a probe day (see `retesting-learned-rules.md`). diff --git a/contrib/inbox/2026-09-27-boot-detect-login-patch.md b/contrib/inbox/2026-09-27-boot-detect-login-patch.md new file mode 100644 index 00000000..e4b5934e --- /dev/null +++ b/contrib/inbox/2026-09-27-boot-detect-login-patch.md @@ -0,0 +1,13 @@ +# Local boot: app-specific login replacement means no local password works + +**Source:** marketplace-rnd `docs/case-study/boot.md`. **Status:** unreviewed. + +The app replaces XAS login with a custom Java listener that verifies against a remote identity +service, so no local/demo password logs in. The project keeps a local patch (skip-worktree) that +the cloud container lacks — UI e2e runs there were blocked, and a test agent misdiagnosed it as +an SSO module problem. + +Proposed for the boot skill / boot script: detect a custom login listener in `javasource/` +(e.g. a class registering a login handler at startup) and report "local login needs a patch" +up front, instead of letting the journey fail at the login step. Project-specific details stay +in the project brain. diff --git a/contrib/inbox/2026-09-27-e2e-helpers-playwright-executable.md b/contrib/inbox/2026-09-27-e2e-helpers-playwright-executable.md new file mode 100644 index 00000000..109c7db8 --- /dev/null +++ b/contrib/inbox/2026-09-27-e2e-helpers-playwright-executable.md @@ -0,0 +1,13 @@ +# project-tests/e2e/helpers.js: no executablePath fallback when the pinned browser is missing + +**Source:** marketplace-rnd `tests/e2e/helpers.js` (2026-09-27). **Status:** unreviewed. + +In cloud containers the pinned headless shell (rev 1243) is absent; only +`/opt/pw-browsers/chromium` (1194) exists, so every e2e launch fails before testing anything. +Project fix: +```js +const exe = process.env.PW_EXECUTABLE + || (require('fs').existsSync('/opt/pw-browsers/chromium') ? '/opt/pw-browsers/chromium' : undefined); +if (exe) launchOpts.executablePath = exe; +``` +Port to the shipped helper (env override first; probe path second). diff --git a/contrib/inbox/2026-09-27-exec-autorestore-no-v1-arm.md b/contrib/inbox/2026-09-27-exec-autorestore-no-v1-arm.md new file mode 100644 index 00000000..480d06ae --- /dev/null +++ b/contrib/inbox/2026-09-27-exec-autorestore-no-v1-arm.md @@ -0,0 +1,15 @@ +# exec.sh inline gate-fail auto-restore never restores a v1 (single-file) model + +**Source:** marketplace-rnd (152 MB v1 .mpr, no `mprcontents/`). **Status:** unreviewed inbox drop. + +## Failure +The gate-fail auto-restore in `project-bin/exec.sh` only restores when `SNAP_UNITS -gt 0` +(counted under `mprcontents/`). On a v1 model the snapshot has none, so it prints +"⚠ Snapshot has no mprcontents/ — refusing to restore from it." and leaves the broken model in +place. `project-bin/restore-mpr.sh` already has a v1 arm (restore the single .mpr file, +~lines 51–60); exec.sh carries an older inline copy of that logic without it. + +## Fix +Have exec.sh call `restore-mpr.sh` instead of its inline copy (one restore implementation), or +port the v1 arm. Fixture: a v1 snapshot + failed gate must end with the .mpr byte-identical to +the snapshot. diff --git a/contrib/inbox/2026-09-27-exec-gate-false-green-version-mismatch.md b/contrib/inbox/2026-09-27-exec-gate-false-green-version-mismatch.md new file mode 100644 index 00000000..3d4fe805 --- /dev/null +++ b/contrib/inbox/2026-09-27-exec-gate-false-green-version-mismatch.md @@ -0,0 +1,26 @@ +# exec.sh gate logs "mxbuild clean" when mxbuild refused the model (version mismatch) + +**Source:** marketplace-rnd (Mendix 11.12.2, v1 single-file .mpr), review +`docs/reviews/2026-09-26-exec-gate-missed-ce0066.md` in that project (full fix diff there). +**Status:** unreviewed inbox drop. **Impact: high** — 29 of 29 Mac exec rows 09-24..09-26 were +false greens; a real CE0066 shipped through the gate. + +## Failure +`find_mxbuild` picked the newest cached mxbuild (11.14.0 Beta), not the model's version. That +mxbuild refuses an 11.12.2 model with **exit 3**, writes the reason to the errors file's +`errors[]`, and leaves `problems[]` empty. The gate counts Error-severity `problems[]`, gets 0, +sets `GATE_STATE="pass"` and never looks at `MXBUILD_EXIT`. BUILD-LOG: "mxbuild clean". + +Bad output (verbatim shape): exec row `gate: pass (0 errors)` while mxbuild exit=3. + +## Fix (from the review's diff) +1. `_common.sh` `mxtk_mxbuild_error_count`: if count is 0 **and** exit != 0 → print `?`, return 1. +2. `exec.sh` (and `verify-model.sh`): non-zero exit with 0 problems → `unverified`, never `pass`. +3. `find_mxbuild`: prefer the cached mxbuild matching the model's `_MetaData._ProductVersion` + (new helper `mxtk_model_version`); only fall back to newest with a loud warning. +4. `find_java`: on mac use `/usr/libexec/java_home -v 21` (platform-guarded). +Stopgap the project used: `.claude/toolkit.env` with `MXBUILD_PATH` and `JAVA_HOME`. + +## Rule to state in a skill +An exit code and a problem count are two facts; "0 problems" from a tool that exited non-zero +means *not measured*, not *clean* (fits `skills/tool-output-is-not-ground-truth.md`). diff --git a/contrib/inbox/2026-09-27-learned-microflow-patterns-stale-ce0639.md b/contrib/inbox/2026-09-27-learned-microflow-patterns-stale-ce0639.md new file mode 100644 index 00000000..dbeb16e7 --- /dev/null +++ b/contrib/inbox/2026-09-27-learned-microflow-patterns-stale-ce0639.md @@ -0,0 +1,13 @@ +# learned-microflow-patterns.md: stale CE0639 claim + contradicts CONV010 + +**Source:** marketplace-rnd guest-groups build. **Status:** unreviewed inbox drop. + +1. `skills/learned-microflow-patterns.md` (~l.397, 409–424) says validation feedback → + "CE0639 unavoidable via mxcli". BUG-47 is resolved; the guest-groups SUB with validation + feedback built with 0 errors on v0.23.0. Stamp the rule as retested/obsolete. +2. Same section recommends validation feedback directly in `ACT_OrderDetail_Save`. CONV010 + (ACT microflow content allowlist) does not allow ValidationFeedback in ACT_ — the pattern + lints red. Recipe: `VAL_`/`SUB_` does the feedback, `ACT_` calls it and branches. +3. Side note: upstream fixed CONV010's merge false positive; the project re-synced to + upstream's rule. Check whether the toolkit's own `lint-rules/conv010_act_microflow_content.star` + replacement is now obsolete. diff --git a/contrib/inbox/2026-09-27-marketplace-rnd-patches.md b/contrib/inbox/2026-09-27-marketplace-rnd-patches.md new file mode 100644 index 00000000..42f4d039 --- /dev/null +++ b/contrib/inbox/2026-09-27-marketplace-rnd-patches.md @@ -0,0 +1,166 @@ +> **Triage note (2026-09-27, hand-checked):** the one LOCAL-FIX (`bin/page-fidelity.js`) is only +> partly a fix that never traveled. Already upstream (85da9d6): ALTER PAGE image bindings and the +> CUT-row skip. NOT upstream yet, worth promoting: `contractRows()` (five-column bind table read +> as the class contract) and `cssCellClasses()` with a `FILE_EXT` set (stops `ds.css` being +> counted as a `.css` class). The shipped copy is ahead on prototype-route support, which the +> project copy lacks — so the promotion is a merge of two functions, not a copy-over. The 11 +> STALE rows are ordinary sync lag (`sync-project.sh --upgrade-bin`), no toolkit action. + +**From:** marketplace-rnd +**Date:** 2026-09-27 +**Kind:** fix +**Field evidence:** installed toolkit scripts in marketplace-rnd/bin that differ from the shipped copy — a local patch here is a fix that never traveled (how graph-sweep's stat bug got patched twice) +**Proposed target:** see per-item notes below + +--- + +- bin/build-plan-status.sh — STALE: identical to shipped 234aa6f (2026-09-08); fix: bin/sync-project.sh --upgrade-bin build-plan-status.sh + +- bin/check-page-shell.sh — STALE: identical to shipped 43622af (2026-09-09); fix: bin/sync-project.sh --upgrade-bin check-page-shell.sh + +- bin/close-task.sh — STALE: identical to shipped fd60fb8 (2026-09-08); fix: bin/sync-project.sh --upgrade-bin close-task.sh + +- bin/conformance-check.sh — STALE: identical to shipped fd60fb8 (2026-09-08); fix: bin/sync-project.sh --upgrade-bin conformance-check.sh + +- bin/coverage-preflight.sh — STALE: identical to shipped fd60fb8 (2026-09-08); fix: bin/sync-project.sh --upgrade-bin coverage-preflight.sh + +- bin/fixture-manifest.sh — STALE: identical to shipped fd60fb8 (2026-09-08); fix: bin/sync-project.sh --upgrade-bin fixture-manifest.sh + +- bin/graph-sweep.sh — STALE: identical to shipped fd60fb8 (2026-09-08); fix: bin/sync-project.sh --upgrade-bin graph-sweep.sh + +- bin/page-scope.sh — STALE: identical to shipped fd60fb8 (2026-09-08); fix: bin/sync-project.sh --upgrade-bin page-scope.sh + +- bin/restore-mpr.sh — STALE: identical to shipped fd60fb8 (2026-09-08); fix: bin/sync-project.sh --upgrade-bin restore-mpr.sh + +- bin/review-module.sh — STALE: identical to shipped fd60fb8 (2026-09-08); fix: bin/sync-project.sh --upgrade-bin review-module.sh + +- bin/verify-module.sh — STALE: identical to shipped fd60fb8 (2026-09-08); fix: bin/sync-project.sh --upgrade-bin verify-module.sh + +## bin/page-fidelity.js differs from shipped project-bin/page-fidelity.js — LOCAL-FIX + +Not byte-identical to any shipped version in toolkit history — a real local fix. Closest historical base: aa6ff98 (2026-09-12), 152 diff line(s) from this project's copy. Diff (shipped -> project), truncated at 120 lines: + +```diff +--- shipped/project-bin/page-fidelity.js ++++ project/bin/page-fidelity.js +@@ -48,37 +48,18 @@ + // scores as what it is. --no-log suppresses logging entirely (for scoring fixtures or + // another project's files); an unloggable run says so on stderr rather than logging + // silently nowhere. +-// +-// A SCREEN IN THE CLICKABLE PROTOTYPE. The wireframe argument may also be a route into the +-// assembled prototype, `design/prototype.html#/order-list` (assemble-prototype.js). The one +-// section is read back out through prototype-route.js, which undoes the only transform that +-// matters to this scorer (scoped screen CSS, where localMockClasses looks), so a screen scores +-// the same as a file and as a route. test-prototype-route.sh pins that. An unknown route exits +-// 2 and names the routes the prototype does have. + 'use strict'; + const fs = require('fs'); + const path = require('path'); +-const proto = require(path.join(__dirname, 'prototype-route.js')); + + const argv = process.argv.slice(2); + const NOLOG = argv.includes('--no-log'); + const STUB = argv.includes('--stub'); + const [WF, PAGE, ...MDLS] = argv.filter(a => a !== '--no-log' && a !== '--stub'); + if (!WF || !PAGE || !MDLS.length) { +- console.error('usage: page-fidelity.js [--no-log] [--stub] '); ++ console.error('usage: page-fidelity.js [--no-log] [--stub] '); + process.exit(2); + } +-const WF_REF = proto.parseRef(WF); +-const WF_FILE = WF_REF ? WF_REF.file : WF; +- +-function readWireframe() { +- if (!fs.existsSync(WF_FILE)) { console.error('page-fidelity: no such wireframe: ' + WF_FILE); process.exit(2); } +- const html = fs.readFileSync(WF_FILE, 'utf8'); +- if (!WF_REF) return html; +- const doc = proto.standalone(html, WF_REF.route); +- if (doc === null) { console.error('page-fidelity: ' + proto.unknownRouteMessage(WF_FILE, WF_REF.route, html)); process.exit(2); } +- return doc; +-} + + // ---- wireframe side ------------------------------------------------------------------- + +@@ -266,11 +247,12 @@ + // in its datasource cell (CamelCase words, Entity.Attr paths) appears in the + // page MDL. Rows whose cell names no identifier are annotation prose and skip. + // +-// A row the author struck through () or whose fifth (Verdict) cell says CUT / NOT +-// BUILDABLE is not owed. bindRows used to skip only header rows, so a wireframe's +-// `Demo chip (curated tiles)` row (verdict "CUT — would be empty on 15 of the 16 +-// tiles") was scored as a missed binding on a page that correctly left it out +-// (marketplace-rnd CatalogView_v5, 2026-09-23). ++// A row the author struck through or marked CUT / NOT BUILDABLE is not owed — the same rule ++// contractRows applies. bindRows used to skip only header rows, so CatalogView-redesign-v2's ++// `Demo chip (curated tiles)` row (verdict "CUT — would be empty on 15 of the 16 tiles") ++// was scored as a missed LastPublishedVersionDemoUrl binding on a page that correctly left it ++// out (marketplace-rnd, 2026-09-23). Two readers of one table disagreeing about which rows count ++// is the defect; notOwed() is the one answer both now use. + const notOwed = trInner => { + if (//i.test(trInner)) return true; + const raw = [...trInner.matchAll(/]*>([\s\S]*?)<\/t[dh]>/gi)].map(c => c[1]); +@@ -293,7 +275,48 @@ + return rows; + } + +-function wfFacts(html) { ++// The CONTRACT: a five-column bind table (Component / Widget / Binding / CSS / Verdict) is the ++// author saying which classes the built page must carry. Measured 2026-09-22 on CatalogView_v5 ++// against CatalogView-redesign-v2.html: the wireframe defines every class in its own