Skip to content
Open
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ Kinds: `new` · `fix` · `learn` (a skill/learning) · `process` (rules, templat
Credit the person or project that surfaced the change — the credit line is the thank-you.

## 2026-09-16
- new(deploy-to-sandbox): **a deployment is untested until a test has run against that URL — local green is a prerequisite, never a substitute.** Seven steps for promoting an app to a cloud sandbox: enumerate what the target lacks (a Mendix Free App Sandbox has no M2EE admin API, no runtime-settings UI, no deploy API, no allowlist, and cold-starts); poll for readiness instead of sleeping for it; audit every guard for a localhost premise; report a non-assertable check as SKIPPED with its denominator, never PASS; verify config through behaviour rather than presence; package it as one command; and decide the handover shape from the evidence. Field evidence 2026-09-16 — an e2e suite that had never once run against its sandbox reported `FAIL demo.admin Administrator 0/6 []` on an app whose navigation was entirely correct. Three localhost assumptions produced it and **none was about the app**: a fixed `waitForTimeout(2500)` tuned to a warm local runtime (a poll for rendered `mx-name-*` widgets read all six items back seconds later); an ownership guard refusing a REMOTE run over a stale LOCAL port out of `stack.env`, whose port-ambiguity premise cannot apply to a named host; and eight journeys whose oracle is the local database reporting a healthy sandbox as broken (`DB read failed ... for PlantA`). After the fix, 4 passed / 0 failed / 8 skipped of 12, and J1 went 0/3 roles to 3/3. Two rules carry most of the value: fix a guard's premise rather than reaching for its `ALLOW_UNVERIFIED=1` escape hatch, which teaches operators to keep the bypass permanently on; and a skipped assertion that reports PASS is green-by-absence — Maurits Visser, from a MOC/PSSR app replacement
- learn(ui-preflight-pages): **Step 0 — enumerate what already reaches a capability before building a page or adding a way into one.** Positive recipe, not a ban: list the existing entry points with the reference tools (`mxcli impact`, `mxcli callers`, `DESCRIBE PAGE <HomePage>` for buttons and snippet calls already placed), then read the target page's own `Layout:`. Two things it prevents, both measured 2026-09-16: `SEARCH '<PageName>'` is a **text grep, not a reference check** — it did not surface the `show_page` action sitting in the home page's widget tree, so two chat pages that already had floating buttons (`btnAgentFab`, `btnGraphFab`, each passing a `ChatContext`) were diagnosed as unreachable; and a browser scrape of navigation captions could not match an **icon-only** floating button, so a second silent instrument agreed with the first and the pair was read as confirmation. The layout half is the sharper rule: a page on a popup layout (`ConversationalUI.Layout_FloatingPopup` and kin) is a **popup, not a destination** — it is opened by a button passing a context object, and a navigation item cannot pass a page parameter, so route one through navigation only where the requirement asks for a dedicated page. Done when you can name every existing entry point and say why another is needed — Maurits Visser, from a MOC/PSSR app replacement
- learn(learned-detection-gaps): register row — **a navigation menu item pointing at a popup-layout or parameterised page passes the entire static ladder.** `check --references` reports **0 errors**, exec succeeds, and `SHOW NAVIGATION MENU` reads the entry back cleanly; the first oracle is live runtime or reading the target page's own `Layout:` and parameter list. Note precisely what the green certifies — the *reference* is valid, the page exists — and that nothing on the ladder checks whether a nav item is a legal way to reach it — Maurits Visser, from a MOC/PSSR app replacement
- process(iterative-build-loop): **"committing after an exec — enumerate, then name", resolving a collision between two of this toolkit's own rules.** `mxcli exec` rewrites the `.mpr` *and* every `mprcontents/*.mxunit` it touched; a blind `git add -A` is banned (it sweeps in a concurrent session's staged work, and in a gutted-tree state commits a ~108 MB blob), so commits pass explicit pathspecs — but **an explicit pathspec cannot name a file you did not know changed.** The resolution is `git status --short` after every exec, before writing the pathspecs, with `mprcontents/` as a directory pathspec being the right grain. Field evidence 2026-09-16: a commit named the `.mpr` and the script and left the exploded navigation unit behind; git reported `2 files changed`, the tree looked finished, and the `.mpr` disagreed with its own contents in the commit — an internally inconsistent Team Server checkout that nothing in the exec, the check or the commit mentioned, surfaced only because a stop hook noticed a dirty tree — Maurits Visser, from a MOC/PSSR app replacement
- new(agent-permission-friction): **two different things refuse an agent's command, they render identically, and only one can be fixed by editing a file — telling them apart is the skill.** A *permission rule* is a deterministic prefix match that ships with the repo and behaves the same for every user on every device; a *safety classifier* refusal is a judgement, exists only in managed/sandboxed environments (cloud, container, web session) where a local CLI user never sees it, carries a bracketed category label, and **cannot be allowlisted away** — so the guidance is: if you cannot tell, assume classifier and reshape, because reshaping is free and rule-editing when the rule was never the problem is what cost roughly **fifteen turns** in the field run (2026-09-16). Four checks before the word *blocked* reaches the user, highest-yield first: (1) **is the command line compound** — a rule matches the START of the line, so `./mxcli exec ...` matches an allowlisted `./mxcli` rule and `cd /path && ./mxcli exec ...` matches **nothing**, which is invisible from the refusal text because the refusal is about `cd`, and the remedy is free since the working directory persists between calls; (2) does it merely *resemble* something dangerous — renaming a credential variable on the command line reads as exfiltration prep, a heredoc near a credential is refused even when the body only *discusses* one (writing the file and passing a path worked every time and the heredoc worked none), and anything that reads as redirecting traffic or rewriting the agent's own config; (3) **a refusal is sticky for the rest of the turn** — an unrelated, fully permitted command was refused with the *same* label and succeeded unchanged on a fresh turn, so a second denial is not evidence about the second command and the move is to end the turn; (4) only then read the settings the harness actually reads, because in the field run the correct rule had been there since scaffold. Completion criterion carries its denominator: the word *blocked* is legal only after naming which of the four checks failed. Carries its own placement rule, learned the same day: this knowledge does **not** go in a project `CLAUDE.md`, which `mxcli init` overwrites and which Copilot/Cursor/Windsurf never read — it lives here, referenced not copied, and reaches projects through the baseline routing table in their generated `CLAUDE.local.md`. And it deliberately ships **no table of harness settings paths or rule syntaxes** (authoring rule 5): every tool has some allow/deny list, they are all spelled differently and they all move, so the file says read your own harness's config and reports only the principle that generalises — Maurits Visser, from a MOC/PSSR app replacement
- process(skill-routing): `agent-permission-friction` routed **baseline** for all agents, trigger-phrased on *"any refused, denied or blocked command — BEFORE rewriting a permission rule and before telling the user a tool is blocked"*. Baseline rather than situational because a session in a denial loop is not browsing a situational table, and on this repo's standing rule that a skill living only there goes unnoticed by projects that are not hunting for it. Baseline budget after the add: **77,323 of 80,000 words** — headroom is now under 3,000, so the next baseline routing needs a displacement, not an append — Maurits Visser, from a MOC/PSSR app replacement
- process(contrib/inbox): **three drops from a deploy-and-configuration retrospective — the arc from first deploy to a configured, working app cost about three days, and five of its six walls were knowable in advance.** (1) *"Read it from the environment" is only a design if the environment can be written to* — an app whose credentials were designed to come from environment variables was deployed to a **Mendix free sandbox, which has no configuration surface at all**: no constants tab in the free-app portal, Deploy API v4 has no constants endpoint (24 environment sub-resources probed, **all 404**), Deploy API v1 has one but is a licensed-node API that rejects PATs by design (four auth-header combinations tried, and a full-rights key authenticates and still does not apply), and the app's own `/xas/` protocol reaches app data and never platform configuration. The tier was in hand the whole time — `"licenseType": "free"` was in the very first `/apps/{id}` response, days earlier — and nothing in the process asked for it, so the proposal is an environment-capability question at architecture time plus the one-call probe that answers it. Carries a correction worth having: **Mendix has no "password constant"** — a constant has Name, Type, Default Value, Documentation, Export Level and Exposed to Client, and hiding a credential from users is attribute-level entity access (the way `MxGenAIConnector` omits `AccessToken` from its read grant), so an instruction to "mark it as a password in Studio Pro" had sat in a repo for weeks being followed by nobody because it could not be followed. (2) *A tolerant parser upstream of a strict one is a silent-failure machine* — a GenAI configuration key supplied at **1345 characters, `1345 % 4 == 1`, which base64 can never be**; Node's decoder swallowed the stray `=` and reported success, Mendix's rejected the import with a message pointing at the portal, i.e. at the one place that was not the problem. Plus the false-green sibling: a chat path returning **HTTP 200 on both calls** with the reply stuck `Loading`, empty, and no trace rows past **153s** — the transport succeeded and the product did not happen. And the convention that did work, a guard string naming which of *unconfigured* / *broken* / *empty* you are in, at the cost of one string literal. (3) *`cd X && cmd` defeats every Bash permission allowlist* — rules match the **start** of the command line, so an allowlisted `./mxcli` call prefixed with `cd` matches nothing; ~15 turns were lost writing new rules that also could not match, against an allowlist that had never needed changing, and the working directory persists between calls so the fix is free. Expensive because session cost tracks turn count × context size, not output length — Maurits Visser, from a MOC/PSSR app replacement
- fix(assemble-prototype.js): **a screen's inline `<script>` is namespaced per route too, not just its ids.** Concatenating twenty screens into one document put every screen's top-level `function name(){}` on the same global object; the last screen's declaration silently won, so another screen's `onclick="toggleCopilot()"` ran the wrong screen's code against the wrong screen's (hidden) elements, with no console error — the same disease `ade3851` fixed for ids, one layer up. Each inline script is now wrapped in an IIFE that exports its functions onto `window.__proto['<route>']`, and `on*=` handler attributes are rewritten to call through it; the selftest (`tests/wave2/test-assemble-prototype.sh`) grew 8 asserts (48/48) covering a same-name collision, an `onclick` with an argument, and a script-less screen. Field run: assembled the 9-screen TCX graph PoC prototype (56 handler/export sites namespaced across 9 routes) and, with Playwright/Chromium, opened it at a non-default route (`#/tfc-npd-gate`) and clicked its `toggleCopilot` button — the effect landed on that route's own `id="tfc-npd-gate--copilot-modal"` only, the default route's modal stayed shut, zero console errors. Also: `data-bind="X"` now follows the `<tr id="X">` it names when that id is namespaced — the first id-namespacing pass renamed the row and left the reference behind, so `check-prototype-links.js` reported every bound control as `unbound` (`tests/wave2/test-prototype-links.sh` went 32/32 → 24/32 and CI caught it on #62; back to 32/32, `test-assemble-prototype.sh` 50/50). — MendixMau (#53)

## 2026-09-15
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,7 @@ Every mxcli project has a `.ai-context/skills/` directory (bundled by `mxcli ini
| Handing a headless-built model to a person — opening it in Studio Pro, a free sandbox, or a colleague's machine: the model travels, the demo data and runtime config (keys, an agent's bound model) do not, and each needs its own re-establish step | `skills/handoff-to-studio-pro.md` |
| Stage 5 start, before the first module of any entry mode — one entity, flow, page, nav, demo user, journey and screenshot proven in the running app, so build/run/look/test are known to work before a module depends on them | `skills/walking-skeleton.md` |
| At project birth (before the first build script) and any time a model needs a platform home: creating the Team Server app, adopting an existing GitHub-born model into it without rewriting history, or deploying; also when the Platform SDK returns 403, git rejects the PAT, a deploy cannot be triggered from a PAT, or the app turns out to be a Free App | `skills/platform-link.md` |
| Promoting an app to a deployed sandbox or cloud node, or before a customer tests a deployment — "it works locally" is not evidence about a deployment; also when a test suite fails only against the remote URL | `skills/deploy-to-sandbox.md` |

**Build · MDL — the language and tool reference**

Expand Down Expand Up @@ -758,6 +759,7 @@ The "When to use which skill" table above is *situational* — load a skill when
| Before calling any module tested — what testing a module means, and the false-green register of confirmed ways a test reports green over a broken feature | `skills/testing-shape.md` |
| Finishing any module — before calling it done. One command that runs every instrument and keeps "instrument faulted" apart from "feature failed"; in a wired project run the installed copy at bin/verify-module.sh | `project-bin/verify-module.sh` |
| Any time an exit code, a tool's output or a subagent's report is about to become a stated finding — verify before you conclude | `skills/tool-output-is-not-ground-truth.md` |
| Any refused, denied or blocked command — BEFORE rewriting a permission rule and before telling the user a tool is blocked. A rule matches the START of the command line, so an allowlisted tool prefixed with cd matches nothing | `skills/agent-permission-friction.md` |
| A style change that appears to have done nothing, or an app still grey after a design port every instrument called green — the three ways a correct rule paints nothing (matches nothing / matches chrome / loses the cascade), the two reads that tell them apart, and the class that arrived in the stylesheet and is bound to no widget | `skills/learned-css-that-never-applied.md` |
| Before trusting a green check/exec/DESCRIBE result as proof, or when a runtime symptom appears over a fully green model — the register of constructs that pass early rungs and fail later ones | `skills/learned-detection-gaps.md` |
| Creating any entity, or calling a module security-ready — entity and grants land in one script, and ready means SHOW SECURITY MATRIX proves it | `skills/security-is-not-a-later-script.md` |
Expand Down
2 changes: 2 additions & 0 deletions ROUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ picks the row up. That is the whole procedure — there is no second list to rem
| Stage 5 start, before the first module of any entry mode — one entity, flow, page, nav, demo user, journey and screenshot proven in the running app, so build/run/look/test are known to work before a module depends on them | `skills/walking-skeleton.md` | mdl,gate,test | 5 | ondemand |
| At project birth (before the first build script) and any time a model needs a platform home: creating the Team Server app, adopting an existing GitHub-born model into it without rewriting history, or deploying; also when the Platform SDK returns 403, git rejects the PAT, a deploy cannot be triggered from a PAT, or the app turns out to be a Free App | `skills/platform-link.md` | all | P,5,6,7 | ondemand |
| Any session that will push a model to Mendix Team Server, and BEFORE telling the user a Team Server push is blocked — which remote is authoritative, settle-then-push order, and the four checks that have to fail first | `skills/teamserver-alignment.md` | mdl,gate,review | 5,6,7 | baseline |
| Promoting an app to a deployed sandbox or cloud node, or before a customer tests a deployment — "it works locally" is not evidence about a deployment; also when a test suite fails only against the remote URL | `skills/deploy-to-sandbox.md` | any | 7,8 | ondemand |

#### Build · MDL — the language and tool reference

Expand Down Expand Up @@ -226,6 +227,7 @@ picks the row up. That is the whole procedure — there is no second list to rem
|---|---|---|---|---|
| Reading a whole class of tool defects (a retest, a new mxcli release, an audit) — for one CE code or symptom use bin/bug-lookup.sh instead; the ledger is 32k words | `bug-logs/mxcli-bugs.md` | mdl,gate | 5,6 | ondemand |
| Any time an exit code, a tool's output or a subagent's report is about to become a stated finding — verify before you conclude | `skills/tool-output-is-not-ground-truth.md` | all | - | baseline |
| Any refused, denied or blocked command — BEFORE rewriting a permission rule and before telling the user a tool is blocked. A rule matches the START of the command line, so an allowlisted tool prefixed with cd matches nothing | `skills/agent-permission-friction.md` | all | - | baseline |
| Studio Pro will not load the project, or the .mpr looks gutted — recover before relaunching SP, never git checkout | `skills/mpr-corruption-and-sp-load-errors.md` | mdl,gate | - | ondemand |
| Preparing an mxcli/Studio Pro bug for submission — scope pinning, read-back-vs-write-path verification, gate-sensitivity negative controls, severity scoping, before it's called filable | `skills/bug-submission-checklist.md` | mdl,gate,review | 5,6 | ondemand |
| A page/grid/combobox renders empty (blank cells, zero rows, zero options) during UI review or an e2e run — before assuming a single cause | `skills/empty-widget-triage.md` | mdl,test,review | 5,6 | ondemand |
Expand Down
Loading