diff --git a/CHANGELOG.md b/CHANGELOG.md index 622e3ac..40eb79c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,50 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are. ## Unreleased - chore(inbox): **triage of `contrib/inbox/`: removed 15 promoted/obsolete files, gave the rest neutral names and scrubbed identifiers.** Gone: the ten machine-diff `*-patches.md` files from 2026-09-14, the 2026-09-27 field-project patch file, the built company-brain idea, and three already-promoted or superseded bug dumps. Kept for now: `2026-09-27-mxcli-upstream-issues.md`, because five drafts under `bug-logs/pending-github-issues/` cite it as their source. Four remaining files were renamed to `field-project-{a,b,c}` names; project and app names, home-style paths and personal names inside the 17 remaining entries were replaced with neutral labels or placeholders (technical content unchanged). — MendixMau +- learn(learned-mdl-preflight, learned-detection-gaps, microflow-preflight, testing-shape, learned-mdl-cannot-express, learned-page-patterns, rest-integration-first-time-right, journey-proof, learned-datagrid-customcontent-binding): **the skills side of BUG-142 to BUG-154.** STOP row 12 rewritten shorter for v0.24.0 — `ContentParams` takes a bare attribute or a quoted literal only, and the `toString(Attr)` fix it taught no longer builds; row 14 notes a narrowing `grant` merges, row 23 that `diff` prints false `-`/`~` lines. Detection gaps: the ContentParams row now covers any object-bound `dynamictext` and says check passes `if … then … else`; reserved words cover entities; new CE1870 row. microflow-preflight: CONV013 false positive and doc-comment round-trip resolved from "Not verified"; error handler without `return` is the second merge-overlap shape. testing-shape: `--test-endpoint` writes into the working model until a clean stop; `run --local` starts with scheduled events off (new register row). Also: five cannot-express rows (tab badge, validate-while-typing, AppTitle, always-open nav groups, sidebar toggle / page-load / Marketplace layout) and the DG2 `emptyplaceholder` correction; control-bar attributes, nested list view `Editable` and REPLACE-not-re-run-safe in page patterns; v0.24.0 REST bullets; two OQL journey traps. Triages three inbox notes (ContentParams, REST/JSON/page field notes, scheduled events) — card-disbursement requirements-driven build +- learn(bug-logs): **thirteen new ledger entries (BUG-142 to BUG-154) and five retests from mxcli v0.24.0 / Mendix 11.13.0.** New: `ContentParams` builds only from a bare attribute or a quoted literal, and `if … then … else` passes check and fails CE1613 (supersedes the BUG-23 fix); theme seeder and Atlas-map gaps; `create user role` omits `CheckSecurity`/`GUID`; a narrowing `grant` merges; `diff` false modifications; check/exec disagreeing on same-script documents; E007 on a last quoted enum literal; a table of check/lint misfires and check-green/mxbuild-red constructs; DG2 and filter definition gaps; `rest call … body $Var` sends the literal text; published-REST, mapping and Java action gaps; OQL `HAVING` and date aggregates; MDL surface gaps. Retests: BUG-08 (REPLACE still collides, and on reused child names), BUG-77 reproduced with its trigger isolated, BUG-92 and BUG-117 field instances, BUG-122 on a snippet list view; the layout-merge draft gains the error-handler shape. Each from a `check`/exec/mxbuild A/B or a BSON read on a scratch copy — card-disbursement requirements-driven build +- learn(learned-dg2-patterns, learned-css-that-never-applied, testing-shape, learned-db-assertions, module-review): **five UI and test-harness notes.** DG2 column sizing at phone width: shrink only the wrapper around text that may ellipsize and floor ids/pills/headers at `max-content` (a blanket `min-width: 0` spilled 20 of 20 pills at 390px); Atlas base properties with no knob (`.btn` font-weight, `.form-group` row layout above 767px) survive a port that trusts the knobs; a gate build while `run --local` serves drops `dist/` and hangs every login (runtime.log shows the 404); `mx.data.get` is disabled in the React client, so data probes go through `mxcli oql --direct`; measure text alignment on glyph ranges, not element boxes — card-disbursement requirements-driven build +- learn(learned-detection-gaps, learned-microflow-patterns, learned-workflow-patterns): **four new detection-gap rows and one closed gap, from mxcli v0.24.0 / Mendix 11.13.0.** An `if` with no `else` inside `on error { }` passes check and fails mxbuild (CE0079/CE0773); a Java action parameter named with a Mendix-reserved word (`Case`) is CE7247 at mxbuild only; a data widget on a page a no-read role can open passed a scratch `mx check` and failed exec's gate with CE2729; `[%CurrentUser%]` in a microflow a REST or scheduled path reaches passes every static rung and every UI journey, then fails in the system session (look the account up by login instead — pattern added). CE7410 (a task page without `System.WorkflowUserTask`) is now refused at `check --references`. The `calculated by` row no longer tells you to verify with `mx.data.get`, which the React client disables — card-disbursement requirements-driven build +- learn(workflow-structure-rules, learned-workflow-patterns, module-folder-convention, learned-mdl-preflight, bug-logs): **five workflow rules re-probed on mxcli v0.24.0 / Mendix 11.13.0.** A forward `JUMP TO` builds clean (the CE6681 row taught direction as a platform rule; it was the jump-named-after-its-target defect, fixed upstream in v0.21.0); an interrupting boundary timer ending in `jump to` or `end workflow` builds clean (BUG-109 stamped no-longer-reproduces); BUG-76 splits — the bare-enum `DECISION` is now refused by `MDL-WF03` and still corrupts when forced, a Boolean `true`/`false` decision builds at 0 errors; `create or modify workflow … folder` places the workflow. New: one interrupting boundary per activity (`MDL-WF15`/CE6697) and no `end workflow` on a non-interrupting path (`MDL-WF08`/CE1844); `SET TASK OUTCOME` needs a signed-in named user ("Only named users can complete user task"), with the run-as-session Java action pattern. Evidence: a six-probe, four-control construct run (check, exec, native `mx check`, describe read-back) and a live proof test — card-disbursement requirements-driven build +- fix(project-tests/e2e/otel.js): **`capture()` pages back to t0 instead of reading only the newest 400 traces.** Jaeger answers the newest `limit` traces and says nothing about the rest; with timers running, 400 traces were two minutes, so a capture with t0 15 minutes back reported 0 errors over 24 real ERROR spans. It now pages by `end` while a page is full and still after t0, dedupes by trace, caps at `OTEL_MAX_PAGES` (25) and marks the result `.truncated` when the cap stops it short. Field run against the live Jaeger: t0 −15 min, 462 spans / 0 errors → 15,439 spans / 24 errors in 11 pages; a step's own capture stays one page — card-disbursement requirements-driven build +- fix(project-bin/constants-audit.sh): **a `__SET_ME__`-style sentinel default reports `SENTINEL`, not `MODEL-SECRET`.** The audit only knew SET vs EMPTY, so the placeholder `learned-constants-and-secrets.md` Step 3 prescribes for a must-override secret got the same verdict as a real password in git and needed a waiver. The test reads the value's shape (`__[A-Za-z0-9_]+__`) and still prints nothing; the skill's verdict table gains the row. Field run on a copy of the model: 4 findings → 3, the hub password constant SENTINEL — card-disbursement requirements-driven build +- fix(review-report.js, coverage-check-all.sh): **a module with one coverage ledger per BRD no longer carries a permanent coverage FAULT over a clean count.** `coverage-check-all.sh` keeps `leaves:` on each per-BRD line, and `review-report.js` sums the counters across those lines (null, never zero, when a BRD line is FAULT / NO LEDGER, lacks a counter, or the lines miss the stated BRD count); an old installed copy without `leaves:` is named in the fault. Field run on two real ledgers: FAULT → pass, leaves 337 / 610, 947 summed over both — card-disbursement requirements-driven build +- fix(project-bin/check-page-shell.sh): **the page-body scan skips quoted strings and block comments.** An unbalanced brace inside a string literal (a JSON payload preview `Content: '{{ … }'`) left the depth up, the scan ran on into the next pages and a page with one H1 was reported as declaring 3; a `--` inside a string cut the line and did the same. Field run: 18 pages across 87 scripts, 1 violation (the false positive) → 0 — card-disbursement requirements-driven build +- learn(skills/testing-shape.md): **`mxcli test --attach` moves the `.mpr` mtime even when every unit comes back identical**, so the catalog reads stale and `verify-module.sh`'s graph sweep FAULTs — run `REFRESH CATALOG FULL` between a seeding test run and the verify — card-disbursement requirements-driven build +- learn(skills/rest-integration-first-time-right.md): **a custom `on error { … }` block on a `rest call` is accepted now; the "CE6035" row was stale.** Probed on a copy of the field model (mxcli v0.24.0, Mendix 11.13.0): `on error { … }` and `on error without rollback { … }` on a REST call outside a loop add no `mx check` error; the same handler inside a `while` is CE0644 ("must be 'Rollback' inside a looped activity"), which the row now says, pointing at the existing per-row-isolation lesson — card-disbursement requirements-driven build +- fix(project-tests/e2e/report-normalize.js): **`--selftest` pins its own walkthrough declarations instead of depending on the project's.** Its cases exercise `full-app-walkthrough` and `mobile-fieldscan`, but `WALKTHROUGHS` came from `project.config.js`, so the selftest failed on the template config (2 FAIL, then a TypeError) and on a project declaring none (4 FAIL, then `canExpressFault` of undefined). Field run in scratch: old 60 / 41 ok and rc 1; new 139 ok, `all ok`, on both the template config and the field project's config. (It still needs a `.mpr` above it: `INPUTS.mpr` reads the config's `mprName` at load.) — card-disbursement requirements-driven build +- fix(project-bin/render-improvement-register.sh): **the skill's Corrections table renders in its own section instead of as blank OPEN findings.** Its header (`# | Date | Claim … | Measurement … | Where the old claim still stands …`) mapped no cell to finding or disposition, so every correction read as an empty finding, OPEN, severity `(none)`, inflating the open count. A header with a Claim column and no Finding/Disposition column is now read as the corrections table. Field run on the project's register: before, 45 rows, open 10, `(none)`=7; after, 38 findings, open 3, 7 corrections listed apart — card-disbursement requirements-driven build +- fix(project-tests/e2e/project.config.template.js): **on a two-tree checkout `id` is the `.mpr`'s name, not `app`.** ROOT resolves to `app/` there, so `id` (and the default `otelService`) read `app`, and every trace assertion queried a service that does not exist; `mxcli run --local --trace` names the service after the `.mpr`. Single-tree projects keep the directory name; a two-tree project with no model yet falls back to the repo directory's name, still without throwing on require. Field run: the installed copy printed `id app`, and the project carried a hand `NAME_OVERRIDE` — card-disbursement requirements-driven build +- learn(bug-logs): **BUG-141 now has a proven fix: condition outcomes go from 0/30 to 30/30 with a `PersistentId`, and a paused instance survives a restart and runs to `Completed`.** `bug-logs/pending-github-issues/bug141-fix.patch` is the upstream commit (three `addFreshPersistentID` lines plus a regression test that fails on all three outcome types without them), `git am`-ready on mxcli `main`. The ledger's open question is also answered by measurement: re-running `create or replace workflow` does not re-mint IDs (54/54, then 84/84 kept, via upstream's `CarryPersistentIDs`), so adopting the fix re-IDs outcomes once and never again. Issue draft updated to match; still to be filed by a maintainer — card-disbursement requirements-driven build +- fix(project-bin/check-design-reaches-app.sh): **tokens are compared by VALUE as well as by name, and the defaults find a two-tree `app/`.** The `mxcli new` 11.13 template ships its own `--mxt-*` token bridge, so a design system on the same vocabulary read "41 of 62 tokens arrived" with the template's teal brand in place of the designed indigo; a new `tokens valued K of M` line and a WARN (with built-vs-designed samples) now say so. The built sheet, `theme/web` and `themesource/` defaults probe `app/` when the `.mpr` lives there (they exited 2 before; `$use-css-variables` read `unknown`). Field run on the pre-port build: 8 of 62 valued, matching the project's hand count — card-disbursement requirements-driven build +- fix(project-bin/conformance-check.sh): **a probe mxcli rejects is `UNRUNNABLE`, never `ABSENT`.** A `Parse error:` or "no describable document named" reply exits non-zero like a genuine miss, so a malformed probe on a not-built row scored `ABSENT` → OK — a false green (`DESCRIBE DEMOUSER ops1` read ABSENT/OK while `DESCRIBE DEMO USER 'ops1'` read PRESENT on the same model). Field run on a copy of the field model with a four-row ledger: before, 4 OK; after, 2 OK + 2 UNRUNNABLE, while a genuine `constant not found` stays ABSENT/OK — card-disbursement requirements-driven build +- fix(project-bin/verify-module.sh): **the coverage rung finds the toolkit's `coverage-check.sh`: `MXTK_ROOT` is now exported once derived.** verify-module read the toolkit root from `CLAUDE.local.md` but never exported it, so the child `coverage-preflight.sh` could not see it and the rung read INSTRUMENT FAULT "coverage-check.sh not found" with the clone resolved a few lines above. With the same one-line export in its installed copy, the field project's coverage rung read `LEVEL 1 · MEASURED` on all four modules. (The other half of the finding, doctor naming `sqlite3`, was already in `bin/doctor.sh`.) — card-disbursement requirements-driven build +- fix(project-tests/e2e/helpers.js): **`navTo()` also clicks top-bar menus (`.mx-navbar a`), not only the navigation tree.** The `mxcli new` template's home page renders its menu as `.mx-navbar` with the same `a[title]` shape, so tree-only found nothing and every journey died at step 1 with "never appeared". Field run: the same one-line widening in the installed copy took J-00 from NOT-REACHED at step 1 to green, 15/15 — card-disbursement requirements-driven build +- fix(project-bin/lint-gate.sh): **lint-gate finds the model in the two-tree layout (`app/*.mpr`).** It probed only `$ROOT/*.mpr`, so on a two-tree project it exited 2 ("no .mpr found") on every exec, `exec.sh` logged "lint could not run", and the lint ratchet was silently off for the whole build. It now probes `app/` next, the same as `_common.sh` `find_mpr` (F-020/F-042 class). Harvested from the field project's patched copy, whose next exec reported "✓ lint: no rule rose vs baseline"; scratch probe on both layouts — card-disbursement requirements-driven build +- learn(skills/brd-to-build-plan.md Step 7, learned-mdl-preflight.md): **demo users get a password: the grammar requires one.** Both skills said to create demo users with no password, using `create demo user "name" with roles "Module"."Role";`. On mxcli v0.24.0 that form does not parse, and neither does `CREATE DEMO USER '' ()` (`mismatched input '(' expecting PASSWORD`). The pattern is now `create demo user '' password '' ();`, with one throwaway policy-compliant value per project, recorded in the build plan. Verified with `mxcli check` on both forms and `mxcli syntax security demo-user` — card-disbursement requirements-driven build +- learn(skills/architecture-blueprint.md): **Step 5's register rows are numbered `OI-n`, not `1`, `2`, `3`.** `gate-check.sh` reads the first cell of every table row in `PROJECT.md` as a Stage field, so the skill's own numbered example made a `| 7 | Behaviour change … |` row count as the cutover decision: Stage 7 reported FAIL on a project that had not reached Stage 4, and a `CONFIRMED` `| 3 | … |` row could pass the Stage 3 ✋ gate. Renumbering the rows `OI-n` cleared it in the field; scoping the gate's parse to the Decisions table is filed separately — card-disbursement requirements-driven build +- fix(bin/gate-check.sh): **the dashboard is named after the resolved project directory, so `gate-check.sh . --html` no longer titles it ". — Conversion Dashboard".** The name was `basename` of the argument as typed. Field run: a scratch project checked with `.` — old title ".", new title the directory name — card-disbursement requirements-driven build +- learn(skills/brd-validation.md, brd-generation.md): **a use case with no code and no document behind it gets `status: proposed`, owed a sign-off.** The status vocabulary was `code-inferred` / `doc-confirmed` / `doc-conflict`, which has no honest value for a use case that is the build's own design: `code-inferred` claims a code source that does not exist. Check 6 now names the case and requires an `openQuestions` entry or a cited `CONFIRMED` register row; `brd-report.sh` already counts only `doc-confirmed` as corroboration, so no script change (21 of 45 use cases on the field BRD) — card-disbursement requirements-driven build +- fix(bin/images-to-md.sh): **a `--out ` run now prints guidance that names ``, not the default root.** The "write …/.md, then --check" hint and every owed `bin/source-ledger.sh mark … --artifact …/manifest.json` line were hard-coded to `analysis/knowledge-base/images/`, so after `--out` the ledger marks cited a manifest that does not exist and the re-check hint dropped the `--out` it needs. Both now use the directory the run wrote, and the hint carries `--out` when it is not the default. Field run: the field project's sources + knowledge base copied to scratch, `--out /images --check` — old printed `--artifact analysis/knowledge-base/images/manifest.json` (described 15 of 15), new prints the `--out` path. Default-root output is unchanged (`tests/wave2/test-images-to-md.sh`) — card-disbursement requirements-driven build (FINDINGS T-6) +- learn(skills/conversion-runbook.md): **the runbook now says which `bin/` a bare `bin/