diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2531c34..2f413d9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,7 +7,14 @@ moment updating it became a separate chore). One line per change:
Kinds: `new` · `fix` · `learn` (a skill/learning) · `process` (rules, templates, CI).
Credit the person or project that surfaced the change — the credit line is the thank-you.
+## 2026-09-10
+- fix(bug-logs/mxcli-bugs, learned-mdl-preflight): **the `marketplace install` STOP (#879) is LIFTED — fixed upstream in v0.21.0, and this toolkit told four days of sessions otherwise.** Install no longer shells out to `mx module-import`; it copies units with mxcli's own writer and MPR v2 survives, with the destructive path now opt-in behind `--allow-format-change` so the corruption can only be produced deliberately. Re-probed 2026-09-10 on a real 501-unit split-model project: **501 → 1075 `.mxunit`, `.mpr` 84 KB → 156 KB** (an index, not the 35 MB blob this bug used to produce), native mxbuild **0 errors**, app boots, two e2e journeys unchanged. **The cost of not re-probing is the finding, not a footnote:** v0.21.0 shipped 2026-09-06 and the project carried *"marketplace install is BLOCKED, needs a human in Studio Pro"* in its register until 2026-09-10 — with both `.mpk` files already downloaded and sitting in the project root the whole time, because the blocker was never the download. `retesting-learned-rules.md` exists for exactly this and nobody ran it; **a STOP is a claim about a binary version and it expires.** Also records the zero-risk way to re-probe a formerly-corrupting command on a real project — `git clone --depth 1 file://$(pwd) /tmp/probe`, install into the clone, count `.mxunit` and run `mx check` there, decide from the measurement — which is cheaper than the documented recovery and answers what a changelog cannot. **Three new traps replaced the old one, all live on v0.21.0 and none self-announcing:** (1) marketplace packages **do not carry their dependencies**, and the failure is not "module missing" but 406 individual `CE1613` element errors — the aggregation one-liner that turns that wall into a shopping list is in the entry, and the closure for Email Connector 6.4.3 is MxModelReflection + Encryption + CommunityCommons, which checks at 0; (2) the two `mxcli fix` repairs the installer prints are **mandatory, not advisory** — 128 units and 12 units changed on the confirming run, and both exist precisely because Mendix's own tools would collapse MPR v2 without them; (3) a **bundled widget can silently fail to land and the installer cannot self-repair it** — `widgets/RichText.mpk` landed on the probe and not on the real run (33 vs 34 widgets, 3 × CE0462 over an otherwise clean model), a re-run refuses with *"already installed"*, and the repair is to `unzip -o -j` it out of the `.mpk` the installer already downloaded, so **count `widgets/` before and after every install**. One thing genuinely still needs Studio Pro and is now scoped rather than assumed: SAML + UserCommons install cleanly but leave `CE0066` ("Entity access is out of date"), there is no `mxcli fix security`, and an MDL `GRANT` was tried and does not clear it — Maurits Visser, from a MOC/PSSR app replacement
+- learn(learned-css-that-never-applied): **the difference between a UI the user called "poor" and one they called "much better", same day, same design system, was that the first pass verified the SCRIPT EXECUTED and the second verified THE PIXEL CHANGED.** Everything green in the first pass was true and irrelevant. Three ways a perfectly correct rule paints nothing, all three met in one field run on a MOC/PSSR app replacement, 2026-09-10: **(1) matches nothing** — `ds.css` styled `.ds-tablewrap`/`table.ds-table`, but DataGrid2 renders a row as `
` and there is no `
` on any page, so not one table rule had EVER applied, through a full design port, two build phases, mxbuild clean, lint clean and two journeys green; and three top-bar selectors (`.navbar`, `header.navbar`, `.mx-layoutgrid-fluid`) were guessed in one sitting and all three matched zero elements, where one read of the live element chain named the real carrier `.mx-scrollcontainer-top.region-topbar`. **(2) matches too much** — the instinct after (1) is to widen, and `.mx-page { background }` painted the nav bar with it: white menu text on a white bar, an invisible top nav. **(3) matches and loses the cascade** — `.mx-listview-empty` named exactly the right element, sat ~11,000 lines after Atlas in the compiled sheet, and still lost: Atlas ships `.mx-listview > ul .mx-listview-empty { border-style: none; background-color: transparent }` at **0,2,1** against a bare class's **0,1,0**, so source order was never consulted; and that declaration means Atlas is *erasing* the element, not merely failing to style it. The diagnostic is two reads before you touch the rule — computed style, then (only if not found) a six-level `parentElement` walk printing tagName+classList — and the three possible answers have three different fixes, so **a blind rewrite would have been wrong in all four field misses, and in case (2) the rewrite WAS the outage**. Completion criterion carries a denominator: *every component class a pass claims to have shipped has one computed-style read against a named element in the running app, or is reported unverified* — the run that justifies it had row tone implemented, screenshotted and **looking fine while rendering a rejected project amber**, caught only by reading two values. Also records the check that comes first and no CSS instrument can see, because nothing is wrong with the CSS: `check-design-reaches-app.sh` counts classes that ARRIVED in the built sheet, and at the moment the user called the UI poor, four complete semantic tint sets, a five-step elevation scale and a styled empty state were all present, all correct, and referenced by **zero widgets** — the design system was finished and the app was grey. Measure it as *N of M component classes have at least one carrier in the model*; the fix for a zero-carrier class is never to rewrite it, it is to bind it. Three of the five improvements the user reacted to were pure binding of CSS already sitting in the file — Maurits Visser, from a MOC/PSSR app replacement
+- learn(learned-mdl-cannot-express): **when MDL cannot set a class, CSS usually can — check before you go looking for the property.** A UI spec carried per-row status tone on three grids as an `M`-sized MDL task prescribing `DynamicRowClass`, a property that exists in neither MDL nor Studio Pro; it had been written months earlier against an imagined API, the same defect as an instrument built against imagined input. The requirement was met with **zero MDL**, because whatever class MDL *can* bind is a hook the stylesheet reads from anywhere in that element's subtree: every grid already bound `DynamicCellClass: 'ds-statecell ds-statecell--'`, so `.tr:has(.ds-statecell--rejected) > .td` gave three pages row tone with no script, no exec and no gate run. Same move fixed a second item the same day — `.ds-card > .ds-cardhead` is unwriteable from a page because a Mendix class lands on its own element only, and `.ds-card:has(> .ds-cardhead) { padding: 0 }` says it. Two caveats: **source order inside your own rule set becomes load-bearing** (an overview row carries two status cells, matched several rules at equal specificity, and rendered a rejected project amber until the rules were reordered weakest-claim-first), and degradation where `:has()` is unsupported is silent and benign — the selector never matches and you get today's appearance, which is why it must be confirmed by value read and not by eye — Maurits Visser, from a MOC/PSSR app replacement
+- process(skill-routing): `learned-css-that-never-applied` routed **baseline** for mdl/review/gate at stages 5–6, trigger-phrased on *"a style change that appears to have done nothing, or an app still grey after a design port every instrument called green"*. Baseline rather than situational on this repo's own rule that a skill living only in the situational table goes unnoticed by projects that are not hunting for it — and not being reached is precisely what this one records — Maurits Visser, from a MOC/PSSR app replacement
+
## 2026-09-09
+- new(project-bin/page-fidelity.js): **`class="bound"` — the wireframe author saying "this text is a sample of bound data".** The scorer already had this concept and applied it structurally: `` is excluded from the text corpus because a wireframe's table cells are sample ROWS and a page that correctly BINDS them contains none of that literal text. The same is true of a bound value anywhere else on the screen, and there the structure gives nothing away — an `` holding a record's number and name looks exactly like an `` holding page copy, and a chip holding a filename looks exactly like a chip holding a label. Measured on a MOC/PSSR app replacement's project detail page, 2026-09-09: **54%**, and all three misses were sample values the page could not contain — the record's own title, and a mocked attachment's filename, twice. The page was right; the denominator was wrong, and the number was about to be read off as a gate verdict. So the marker is **DECLARED, not guessed**: an element carrying `bound` leaves both the text corpus and the class denominator, and guessing was the alternative and does not work — a heading is bound or it is not, and only the person who drew the screen knows which. Put it on the smallest element that holds only the value (`` leaves no heading behind; `` inside a chip leaves the chip). Fixture grows to **18 assertions** including that a bound heading leaves the DENOMINATOR rather than merely stopping being reported as missed — otherwise the score would be 1 of 2 and still look clean. Known-bad control without the marker: **5 of 18 fail**. Same page scores 100% (3/3 headings, 2/2 content) with it — Maurits Visser, from a MOC/PSSR app replacement
- process(bug-logs): BUG-114 filed upstream as mendixlabs/mxcli#1076 with BUG-118 as its second reproduction; both drafts and the ledger header stamped FILED — the toolkit merge queue
- fix(project-bin/page-fidelity.js): **structure classed as bound data, and the score of record silently going `null%` again.** A wireframe's own ` |