Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are. Named m

## Unreleased

- process(inbox): **five field notes from an existing-app legacy-widget rebuild** — exec.sh keeps an MDL write when mxbuild crashes on it and the next exec runs on "Baseline NOT measured"; a signal stop of `run --test-endpoint` leaves AfterStartupMicroflow on `MxTest.RegisterEndpoint` (second field case for #244); Unit-row delete refused as ORPHAN, rename-then-drop works; a durable-rebuild kit with a "restate from DESCRIBE" STOP row (DESCRIBE omits module-role and layout-row visibility: 97 role locks in 16 documents, none shown); learned entries for File Uploader image mode (CE0463), native-mapped `radiobuttons`/`image`, tab-page `CaptionParams`/`Visible:` silently dropped, BUG-39 on widget XPath, and two-hop data-source paths. Three further notes (restore-mpr usage, build-plan-status flag order, another project's mxcli test blocking exec.sh) were dropped because master already fixes them. Mendix 11.14.0 Beta, mxcli v0.25.0, toolkit c1efcb3 — a field project (anonymised)
- fix(conformance-check): read ledger rows whose pointer cell is bare (no backticks) and treat status `planned` as not-built; a ledger whose acceptance text is also bare prose is still not measured (acceptance must hold a backticked SHOW/DESCRIBE), but it now exits 2 with a FAULT that says rows were found and the text is prose, instead of "shape changed or glob matched nothing" (TK-006, partial: that ledger shape is diagnosed, not fixed) — field report TK-006
- fix(app-restart): **a secret with spaces in quotes is masked whole, and `Pwd` and `Credential` join the secret words.** Follow-up to #403: `--constant "Mod.ApiKey=two words"` and `--db-password 'two words'` printed everything after the first word (the quoted-flag case leaked before #403 too). `mask()` now masks each argument before quoting it for display, so the whole quoted value goes; the executed command is untouched, and a real-launch test proves the started app receives the raw argv; an env assignment at the start of a `--cmd` string keeps the whole command visible; the per-argument seds only run for arguments holding a secret word. The substring match still over-masks names like `Monkey` (Key) and `Turkey`; that is display only, kept, and pinned by a test. `tests/wave2/test-app-restart.sh` gains 22 cases, 8 of them failing on master. — review of #403
- fix(install-manifest): **a clean toolkit checkout passes its own `project-bin/` manifest self-check, so adopters no longer see the "named by NEITHER list" warning at the top of every sync.** `coverage-check-all.sh` (found by `review-module.sh` through its `_tool` lookup in the toolkit clone) and `ts-sync.sh` (UNPROVEN and SUPERSEDED per its header) join `MXTK_PROJECT_BIN_NOINSTALL` with reasons. New `tests/wave2/test-manifest-clean-checkout.sh` fails, rather than warns, when a `project-bin/` file is in neither list. — joshuamoesa (#381)
Expand Down
83 changes: 83 additions & 0 deletions contrib/inbox/2026-10-13-durable-rebuild-kit-legacy-widgets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Durable rebuild of legacy-widget pages: a four-part kit, and "restate from DESCRIBE" as a STOP item

**From:** a field project (anonymised) — an existing Mendix app (old widgets throughout) changed with mxcli + this toolkit
**Date:** 2026-10-13
**Kind:** process
**Field evidence:** 79 non-writable widgets on 40 live documents (DESCRIBE `-- NOT re-executable` count)
and 97 role-locked widgets in 16 documents (BSON scan; DESCRIBE showed none). 34 pages and snippets
were restated whole; the read-back afterwards found 0 non-writable widgets and 0 role locks on them.
Two drafted scripts would have silently dropped role or row visibility had they run. Mendix 11.14.0
Beta (Classic client, Production security), mxcli v0.25.0, toolkit c1efcb3.
**Proposed target:** `skills/existing-app-change.md` (a "legacy-widget pages" section), a STOP row in
`skills/learned-mdl-preflight.md`, possibly a `project-bin/` scan once field-proven as a file

**Currency check:** 2026-10-16 against master d1cbd41: partly stale. STOP row 21 in `skills/learned-mdl-preflight.md` now covers restating a document another script also edits. Still open: the legacy-widget section in `skills/existing-app-change.md` and the scan for role locks that DESCRIBE does not show.
---

## What is already covered

- `bug-logs/mxcli-bugs.md` BUG-115 addendum: "Grep a generated script for `NOT re-executable` before
executing it" (for the File Manager widget).
- `bug-logs/mxcli-bugs.md` (the `hasRole()` entry): no MDL syntax writes `ConditionalVisibilitySettings.ModuleRoles`.
- `skills/learned-mdl-cannot-express.md` (master bda5bed, #277): "A widget visible to one role and not
another — nothing", with the execute-grant recipe for action buttons.

## What is not covered (findings)

1. **DESCRIBE does not print module-role visibility.** One portal page had 19 role-locked widgets; DESCRIBE
printed none of them (nor a hidden "show page" action behind 9 image widgets). A page restated from that
DESCRIBE builds clean and shows every widget to every role.
2. **A drafted restatement said "the snippet has no visibility rules".** It restated a side-menu snippet
from DESCRIBE (navigation-list items have no names, so one item cannot be dropped alone). A BSON audit
found 13 role-locked items. Running the draft would have exposed all of them, with a clean build.
3. **DESCRIBE does not print layout-grid row visibility.** Two form pages had nine detail sections shown
by type through row visibility; a snippet had 56 row-level "show only when it has a value" rules. None
appear in DESCRIBE. The drafters re-wrote them as container `Visible:` (which DESCRIBE does print and
mxcli does write); an audit confirmed every rule was carried over.
4. **Tab-page `Visible:` and `CaptionParams` are accepted and not written** (see the learned-entries note).
Found only by reading the unit back after exec.

So "copy the page from DESCRIBE, edit, `create or replace`" is silently lossy on any app built in Studio Pro.

## Proposed STOP row (learned-mdl-preflight.md)

| # | Operation | Why STOP | Do instead |
|---|---|---|---|
| new | Restate an existing page, snippet or layout from `describe` output (`create or replace`, or `alter … replace` of a container) | DESCRIBE omits module-role visibility, layout-grid row/column visibility and legacy-widget internals; check, exec and mxbuild all pass while the rules vanish | Run the visibility scan (below) on the unit first. Zero role locks and zero row rules: go. Otherwise rewrite each rule as container `Visible:` or flow access, or leave the page to `alter page` / Studio Pro |

Probe that falsifies it: in Studio Pro, set one widget visible to one module role and one layout row
visible on an attribute; `describe page`; grep the output for the role name and the attribute. Today
(v0.25.0): 0 hits for both.

## The kit (each part was done by hand in the field; none is a toolkit file yet)

1. **BSON visibility scan, per document.** Decode each page/snippet/layout unit under `mprcontents/`
(the toolkit's `project-bin/mxunit_bson.py` codec is enough) and count (a) any
`ConditionalVisibilitySettings` whose `ModuleRoles` list is non-empty, (b) any
`ConditionalVisibilitySettings` on a `Forms$LayoutGridRow` or `Forms$LayoutGridColumn`. Report per
document: widget name, kind, roles or attribute. The field scans were inline one-offs, not a saved
script; a shipped version needs its own fixture.
2. **Non-writable count, per document.** `describe` every page, snippet and layout in the app's own
modules (Marketplace modules excluded) and count `-- NOT re-executable` lines, grouped by the BSON
type in the comment. Field result: `Forms$DropDown` 29, `Forms$ReferenceSelector` 27,
`Forms$DataGrid` 16, sign-in widgets 4, `Forms$TemplateGrid` 1, `Forms$ImageUploader` 1,
`Forms$SidebarToggleButton` 1.
3. **Rebuild brief per document.** Inventory first (DESCRIBE plus the scan), then each widget marked
KEEP / CUT / MOVED / NEW against the wireframe, then the restatement. Replacements used:
drop-down → `combobox` on the enumeration; reference selector → `combobox` on the association (a
microflow data source when the filter needs the context object); old data grid → Data grid 2;
template grid → `listview`; image uploader → File Uploader in files mode plus a `dynamicimage`;
role-only visibility → a per-viewer flag from a data-source microflow, or flow access (Mendix hides a
button whose flow the user cannot run). The restating script becomes the document's single owner;
older `alter page` blocks on it are marked superseded (compare #269).
4. **Post-exec read-back.** After each exec: DESCRIBE again (0 `NOT re-executable`), rerun the scan
(0 role locks unless intended), and grep the unit for every `Visible:` / `CaptionParams` the script
wrote. Item 4 above was caught only this way.

Cost, for scale: six drafting agents in parallel; one gated exec per document (about 5–10 minutes each
with mxbuild and lint), serial because there is one writer to the `.mpr`.

Upstream asks (mxcli), for the maintainer to file or link: print module-role and row/column visibility in
DESCRIBE (or at least a `-- role-based visibility not shown (N roles)` comment), accept it on write, give
navigation-list items stable names, and a legacy-widget migration (`DataGrid` → Data grid 2,
`ReferenceSelector`/`DropDown` → combobox) behind a dry run.
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# exec.sh keeps an MDL write when mxbuild crashes after it, and the next exec runs on an unloadable model

**From:** a field project (anonymised) — an existing Mendix app being changed with mxcli + this toolkit
**Date:** 2026-10-13
**Kind:** bug
**Field evidence:** the project's BUILD-LOG shows two "gate could not run" rows 46 s apart: the
script that made the model unloadable, then the next script, which exec.sh accepted and ran on top
of it. Mendix 11.14.0 Beta (Classic client), mxcli v0.25.0, toolkit commit c1efcb3 (`project-bin/exec.sh`
is byte-identical on master 0c62f6f, blob a03adec).
**Proposed target:** `project-bin/exec.sh` (gate branch "mxbuild failed to run" and the pre-flight
"Baseline NOT measured" branch); one line in `skills/iterative-build-loop.md`

---

## What happened (finding, reproduced once in the field)

A page script set a data view's data source to a two-hop association path in its full form
(`$Parent/MyModule.A_B/MyModule.B/MyModule.B_C/MyModule.C`). `mxcli check` passed. After the
exec, the mxbuild loader itself crashed instead of reporting CE errors:

```
An error occurred when trying to set the 'DestinationEntity' property of a Entity ref step in a Page.
```

exec.sh printed:

```
✗ mxbuild failed to run (exit 1) — gate could not verify the model.
→ Snapshot preserved. Open in SP to verify manually before proceeding.
```

and kept the write. It did exit 1 and cleared the verification stamp (good), but the agent driving
the build ran the next script. exec.sh's pre-flight printed `⚠ Baseline NOT measured: mxbuild exited 1
without checking the model.`, carried on, applied the next script, and hit the same crash.

BUILD-LOG rows, verbatim except the script names:

```
| 2026-10-13T12:26:18 | `25g-PageA-rebuild.mdl` | fail | ❌ gate failed | 5 error(s): CE1613 — script rolled back · approval: auto (PROJECT.md) |
| 2026-10-13T12:28:46 | `25g-PageA-rebuild.mdl` | fail | ❌ gate could not run | mxbuild exit 1 · approval: auto (PROJECT.md) |
| 2026-10-13T12:29:32 | `25h-SnippetB-rebuild.mdl` | fail | ❌ gate could not run | mxbuild exit 1 · approval: auto (PROJECT.md) |
```

Recovery: `./bin/restore-mpr.sh <full path to the snapshot taken before 25g>` then
`./bin/verify-model.sh` (0 errors). The 25h row had to be re-run afterwards.

## Why it matters

- The header comment of exec.sh says an unverified gate is rolled back **for `--patch` only**. That is
true and deliberate, but for MDL the "kept" case was designed for "mxbuild could not start"
(wrong version, no JDK). Here mxbuild started, ran on the baseline cleanly a minute earlier, and
crashed only on the written model. That is a property of the write, not of the machine.
- The next exec has no way to tell the two apart: "Baseline NOT measured" is a warning, not a refusal.
So in an unattended chain the damage compounds: every following script is written onto a model
nothing can load, and each one gets its own snapshot of the broken state.

## Proposed fix (hypothesis, not built)

1. **Restore when mxbuild ran clean on the baseline but cannot run after the write.** exec.sh already
has both facts: the pre-flight measured a baseline (`BASE_KNOWN=1`) and the post-exec run exits
non-zero with no errors file. In that case, call `restore_snapshot` exactly as the `--patch` path
does, and log `↩ rolled back: mxbuild crashed on the written model`. Keep today's behaviour when the
baseline itself was not measured (that is the real "machine cannot build" case).
2. **Refuse a write when the baseline cannot be measured** and the last BUILD-LOG row for this model
is `gate could not run`, with one STOP line naming `bin/restore-mpr.sh` and the snapshot to restore.
`ALLOW_UNVERIFIED=1` stays the explicit override.
3. Reword the kept-write message: "Open in SP to verify" is not something an agent can do. Something
like `STOP: the model may not load. Restore with ./bin/restore-mpr.sh <snapshot path>` is actionable.

Probe that would falsify the claim (for the retest fixture): a fake mxbuild that exits 0 on the
baseline and exits 1 with no errors file after the exec; today exec.sh exits 1 and leaves the
written model in place; after the fix the model on disk equals the snapshot.

Related: `--patch` already restores on an unverified gate (header lines 25–29); PR #304 touches the
same file (per-project lock) but not this branch.
Loading