Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
81c057b
fix: write a decision's rule call instead of dropping its condition
claude Aug 21, 2026
c24a53f
fix: refuse a rule or microflow call where a Mendix expression cannot…
claude Aug 21, 2026
a54dc34
fix: parse a qualified call in exprcheck instead of reporting its par…
claude Aug 21, 2026
408a58d
fix: record the rule a decision calls as a reference
claude Aug 21, 2026
47bc472
docs(proposal): full rule document support — read, author, describe, …
claude Aug 21, 2026
b9c2c31
fix(mcp): deduplicate workflow activity names and settle validation (…
ako Aug 21, 2026
c97eda1
docs(proposal): pin the rule document shape against Studio Pro
claude Aug 21, 2026
37a1606
fix(test): stop a file header swallowing the first test, and evaluate…
claude Aug 21, 2026
750b97f
Merge pull request #204 from ako/claude/issue-945-docker-studio-pro-2…
ako Aug 21, 2026
f5f5c54
Fix gen binding AdminUserRole/GuestUserRole under their SDK names
claude Aug 21, 2026
74e66cf
Add ALTER PROJECT SECURITY GUEST ACCESS
claude Aug 21, 2026
831068d
docs(proposal): a foldered rule is stored like any other document
claude Aug 21, 2026
d48bdeb
fix(test): read a test annotation only when it opens its line
claude Aug 21, 2026
850808f
fix(test): refuse an @expect on a test that expects an exception
claude Aug 21, 2026
87e83eb
docs(test): describe the test format that mxcli actually runs
claude Aug 21, 2026
bd96115
docs(proposal): the rule call now matches a Studio Pro document, meas…
claude Aug 21, 2026
59cf077
Merge branch 'main' into claude/mxcli-unit-test-perf-n7ggx8
claude Aug 21, 2026
340c2bd
docs(proposal): settle the rule design — a rule is handled like a nan…
claude Aug 21, 2026
73c6aaf
fix(domain-model): DROP ATTRIBUTE left orphaned validation rules (CE1…
claude Aug 21, 2026
c1f7da5
feat(rules): read a rule — LIST RULES and DESCRIBE RULE
claude Aug 21, 2026
a3554cf
fix(workflow): keep the name on a same-name REPLACE ACTIVITY (#944)
ako Aug 21, 2026
4d934e2
fix(canon): carry workflow PersistentId across a rebuild (#949)
ako Aug 21, 2026
cd3bbef
fix(pages): refuse a show_page argument that is not the context object
claude Aug 21, 2026
9b55ebe
feat(rules): make a rule a catalog object and walk its body for refer…
claude Aug 21, 2026
ab60610
Merge pull request #205 from ako/claude/mxcli-findings-nnl181
ako Aug 21, 2026
47e4d56
Merge pull request #207 from ako/claude/mxcli-unit-test-perf-n7ggx8
ako Aug 21, 2026
ed54f12
Merge branch 'main' into fix/944-replace-activity-same-name
ako Aug 21, 2026
a8180d9
Merge pull request #208 from ako/fix/944-replace-activity-same-name
ako Aug 21, 2026
072368d
Merge branch 'main' into claude/banking-app-feedback-do04dy
ako Aug 21, 2026
39bc3d9
Merge pull request #209 from ako/claude/banking-app-feedback-do04dy
ako Aug 21, 2026
a9bd2f7
docs(proposal): a design for @setup, the annotation that does nothing
claude Aug 21, 2026
283ce41
feat(rules): author a rule — CREATE, DROP, MOVE and the restriction v…
claude Aug 21, 2026
4d92ec8
Merge branch 'main' into fix/949-carry-workflow-persistentid
ako Aug 21, 2026
3111d97
Merge pull request #210 from ako/fix/949-carry-workflow-persistentid
ako Aug 21, 2026
d58b902
fix(executor): validate a workflow's references (#943)
ako Aug 21, 2026
942a618
Merge branch 'main' into claude/bootstrap-prompt-smaller-37u3fu
ako Aug 21, 2026
aeb8425
Merge pull request #211 from ako/claude/bootstrap-prompt-smaller-37u3fu
ako Aug 21, 2026
b24ce0a
docs(rules): surface rules in the syntax topic, quick reference and s…
claude Aug 21, 2026
ec51c4c
test(rules): skip the rule doctype script on the legacy engine
claude Aug 21, 2026
1a47740
Merge remote-tracking branch 'origin/main' into claude/sudoku-test-is…
claude Aug 21, 2026
71556cf
fix(workflow): stop CREATE OR REPLACE deleting boundary events (#948)
ako Aug 21, 2026
3566a36
test(workflow): create the task pages the roundtrip tests reference (…
ako Aug 21, 2026
8248b28
Merge pull request #206 from ako/claude/sudoku-test-issue-46-iyxn30
ako Aug 21, 2026
f0a8766
test(workflow): run the boundary-event roundtrips on both engines (#948)
ako Aug 21, 2026
27173cc
fix(associations): DELETE_BEHAVIOR PREVENT was stored as keep-referen…
claude Aug 21, 2026
93a8000
feat(test): implement @setup, the annotation that did nothing
claude Aug 21, 2026
0478f6c
Merge branch 'main' into claude/mxcli-unit-test-perf-n7ggx8
claude Aug 21, 2026
d9ac66e
Merge pull request #212 from ako/fix/943-workflow-reference-validation
ako Aug 21, 2026
749e5d0
Merge branch 'main' into fix/948-workflow-boundary-event-loss
ako Aug 21, 2026
6171cb4
Merge pull request #213 from ako/fix/948-workflow-boundary-event-loss
ako Aug 21, 2026
dc868a1
Merge branch 'main' into claude/banking-app-feedback-do04dy
ako Aug 21, 2026
8dc1062
Merge pull request #215 from ako/claude/banking-app-feedback-do04dy
ako Aug 21, 2026
047ede5
Merge pull request #216 from ako/claude/mxcli-unit-test-perf-n7ggx8
ako Aug 21, 2026
bc2db0e
docs(skills): add mock-rest-apis, the missing REST-mocking guidance
ako Aug 21, 2026
5cafc2b
docs(skill): point the workflow skill at the runtime skills (#948)
ako Aug 21, 2026
fcd6b2a
Merge pull request #217 from ako/docs/948-workflow-skill-runtime-link…
ako Aug 21, 2026
9972063
Merge pull request #218 from ako/claude/mxcli-findings-nnl181
ako Aug 21, 2026
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
15 changes: 15 additions & 0 deletions .claude/skills/fix-issue.md

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions .claude/skills/mendix/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Detailed syntax for each MDL document type:
| [mdl-entities.md](mdl-entities.md) | Entity, attribute, association syntax | Creating domain models |
| [write-microflows.md](write-microflows.md) | Microflow syntax reference | Writing microflow logic |
| [write-nanoflows.md](write-nanoflows.md) | Nanoflow syntax reference | Writing client-side nanoflow logic |
| [write-rules.md](write-rules.md) | Rule syntax reference | Writing reusable decision logic a decision calls |
| [write-oql-queries.md](write-oql-queries.md) | OQL query syntax | Creating VIEW entities |
| [create-page.md](create-page.md) | Page and widget syntax | Creating pages |
| [fragments.md](fragments.md) | Fragment (reusable widget group) syntax | Reusing widget patterns across pages |
Expand All @@ -46,6 +47,7 @@ External system integration:
| [demo-data.md](demo-data.md) | Demo data & IMPORT | Seeding data, `import from` bulk import from external DB |
| [rest-client.md](rest-client.md) | REST API consumption | Calling external REST APIs via consumed REST client documents |
| [rest-call-from-json.md](rest-call-from-json.md) | REST CALL end-to-end | JSON Structure → Entities → Import Mapping → REST CALL microflow |
| [mock-rest-apis.md](mock-rest-apis.md) | Mock a REST dependency | Building or debugging a REST integration without the live API; forcing 404/500; running offline or in CI |
| [json-structures-and-mappings.md](json-structures-and-mappings.md) | JSON structures & mappings | CREATE/DESCRIBE JSON structures, import/export mappings, domain model patterns |
| [java-actions.md](java-actions.md) | Custom Java actions | Extending with Java code |
| [download-marketplace-content.md](download-marketplace-content.md) | Marketplace download & install | Adding a marketplace module/widget; downloading a `.mpk`; module-update caveat |
Expand Down
7 changes: 7 additions & 0 deletions .claude/skills/mendix/create-page.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,13 @@ describe icon collection Atlas_Core.Atlas_Filled -- every icon + its reference
- `action: show_page Module.PageName(Param: $value)` - Navigate with parameters
- `action: show_page Module.PageName($Param = $value)` - Also accepted (microflow-style)
- `action: create_object Module.Entity then show_page Module.PageName` - Create and navigate
- **A `show_page` argument must be the context object.** Mendix takes the page
argument from the enclosing data widget, so the only spellings that mean
anything are `$currentObject` or the name of the variable that widget is bound
to (`datasource: $Customer` → `(Customer: $Customer)` is fine). Naming any other
variable is refused as **MDL-PAGEARG01** — it used to be accepted and silently
opened the page with the context object anyway. To open a page with something
else, call a microflow that shows it.

**Button Styles:** `default`, `primary`, `success`, `info`, `warning`, `danger`, `inverse`
- Case-insensitive (`primary` and `Primary` both work).
Expand Down
37 changes: 37 additions & 0 deletions .claude/skills/mendix/manage-security.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,43 @@ alter project security demo users on;
alter project security demo users off;
```

### Guest (Anonymous) Access

Anonymous access is what makes part of an app public — a product catalogue anyone
can browse without signing in. It is one flag plus a user role, and the role is
the important half: **whatever that role can read is the app's public surface.**

```sql
-- The role anonymous visitors are given. System.User is what lets an
-- unauthenticated session exist at all.
create user role Anonymous (Shop.Viewer, System.User);

alter project security guest access on role Anonymous;

-- Now grant exactly what should be public — and nothing else.
grant Anonymous on Shop.Product (read *);

-- Re-enabling later does not need the role retyped; the stored one is used.
alter project security guest access off;
alter project security guest access on;
```

Three things worth knowing:

- **The role is mandatory.** Mendix fails the build with **CE0133** ("No user role
for anonymous users selected even though the feature anonymous users is
enabled") when access is on with no role. `guest access on` is refused unless a
role is given or one is already stored.
- **Mendix does not check the role exists**, so mxcli does. A misspelled role
would otherwise build with zero errors and leave anonymous visitors with no
access at all — a broken public site that passes every check.
- **`off` keeps the stored role**, so toggling access while testing does not lose
it. Guest access off with a role set is valid Mendix.

Review anonymous entity access the way lint rule **SEC004** asks you to: any
unconstrained `read *` granted to the anonymous role is readable by the whole
internet (DIVD-2022-00019). Add an XPath constraint or do not grant it.

### Demo Users

```sql
Expand Down
194 changes: 194 additions & 0 deletions .claude/skills/mendix/mock-rest-apis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
# Mock REST APIs Skill

Use this skill when a REST integration needs an endpoint you control instead of a
live third-party API — while building it, while reproducing a bug, or while
verifying the app in a browser or a test run.

Developing against the real API means network, rate limits, credentials, and a
payload that can change under you. None of that is where Mendix integration
defects live: those are in the mapping, the entity types, the error handler, and
the BSON. A mock removes the variables that are not the bug.

## When to Use This Skill

- Building a REST client or `REST CALL` microflow before (or without) real credentials
- Reproducing a payload-shaped bug **deterministically** — a shape small enough to read, that behaves the same on every run
- Exercising error paths: 404, 500, a timeout, a 401 from missing auth
- Verifying the app (`test-app.md`) or running a suite (`test-microflows.md`) offline or in CI
- Redirecting the outbound calls of an app whose model you must not edit

## Two separate problems

Almost every wasted hour here comes from conflating them:

| Problem | Answer |
|---|---|
| **Something must answer the request** | A mock server: Prism (from a contract), WireMock, mitmproxy |
| **The app must send the request there** | A constant, a `BaseUrl`, or a forward proxy — see below |

A mock server is **not** an interceptor. Prism serves one contract at one port
and answers only clients that address it. Asking it to "catch all calls the app
already makes" is a category error — that is the forward-proxy job, further down.

## 1. Point the app at the mock

Three routes, cheapest first. Pick by how the URL is built.

### The URL is built in the microflow — use a constant, change nothing per run

A `REST CALL` URL is an **expression**, so it can be assembled from a constant
(`@Module.Constant` is Mendix's constant reference — `$Name` is a *variable*):

```sql
create constant MyModule.ApiBaseUrl type String default 'https://api.example.com/v1';

create microflow MyModule.CallApi() returns string
begin
$response = rest call get @MyModule.ApiBaseUrl + '/rates'
header Accept = 'application/json'
returns string;
return $response;
end;
```

Then swap the endpoint per run, with no model change and nothing committed:

```bash
# this run only — never written to the project
mxcli run --local -p app.mpr --constant MyModule.ApiBaseUrl=http://127.0.0.1:4020

# a test suite against the mock (--constant needs --local)
mxcli test tests/ -p app.mpr --local --constant MyModule.ApiBaseUrl=http://127.0.0.1:4020

# machine-local default, gitignored: every run picks it up
mxcli constant set MyModule.ApiBaseUrl http://127.0.0.1:4020 -p app.mpr

# flip it on an app that is already running
mxcli constant set MyModule.ApiBaseUrl http://127.0.0.1:4020 -p app.mpr --apply
```

`constant set` refuses a name the project does not define, so a typo cannot
silently apply to nothing. `mxcli constant list -p app.mpr` shows the winning
value for every constant **and which layer set it** — read it first whenever a
run does not use the endpoint you expected.

### The call goes through a REST client document — rewrite `BaseUrl`

A REST client document's `BaseUrl` is a **literal**; it cannot reference a
constant. Point it at the mock by re-running the create, which is a one-line diff:

```sql
create or modify rest client MyModule.RatesAPI (
OpenAPI: 'specs/rates.json',
BaseUrl: 'http://127.0.0.1:4020'
);
```

`BaseUrl` also overrides `servers[0].url` at import time, so one contract can be
imported against the mock and later re-pointed at production.

### You cannot edit the model at all — use a forward proxy

See §3. This is the most work and the last resort.

## 2. Prism: serve an OpenAPI contract as a mock

```bash
npm install -g @stoplight/prism-cli # ~15s
prism mock specs/rates.json --port 4020 # serves the contract's `example` values
```

Everything below cost real time to find out and is not on Prism's front page:

- **Prism mounts paths at the root** and ignores any base path in
`servers[0].url`. Address it as `http://127.0.0.1:4020`, not
`http://127.0.0.1:4020/v1` — otherwise every path 404s while the server looks
perfectly healthy.
- **Make `servers[0].url` absolute in the contract you import.** mxcli's OpenAPI
import only accepts an `http://` or `https://` URL as `BaseUrl`; a relative one
(`/api/v3`) is skipped with the warning *"server URL … is relative and cannot
be used as BaseUrl; set BaseUrl explicitly in CREATE REST CLIENT"*, and a
client with no `BaseUrl` fails at call time, not at import time.
- **`Prefer: code=404`** on the request forces any status the contract documents.
This is the only practical way to drive a Mendix error handler through a real
HTTP response rather than by hand-editing the model.
- **`prism mock -d`** returns schema-generated random data instead of the
`example` values. Run the suite both ways: a mapping that quietly depends on
one fixed payload passes under `example` and fails under `-d`.
- **Prism enforces the contract's `security`**, so a call with no `Authorization`
header gets a real 401. Useful — but know the ceiling before you design around
it: a REST client document's header value may be a literal, a `$Variable`, or a
literal **prefix** plus a variable (`'Bearer ' + $Token`), and nothing else. A
token that must be computed per call belongs in a `REST CALL` expression, not
in the document.
- **Cut a subset; never point Prism at a vendor's full contract.** The official
Microsoft Graph spec is 41 MB of YAML: it downloads in seconds and Prism was
still printing "Starting Prism…" when killed at a 300-second cap. Importing it
would also generate thousands of operations into the module.

A contract small enough to read is the point. Hand-cut one path with one
`example` per response code you care about, and keep it in the project under
`specs/` next to the `.mpr` — the same relative path the `OpenAPI:` clause takes.

## 3. Forward proxy: when the model cannot change

For an app whose model you must not touch, redirect the JVM instead. The Mendix
runtime honours the standard Java proxy properties, and `mxcli run --local`
passes your environment through to the runtime JVM — including `JAVA_TOOL_OPTIONS`,
which mxcli **appends** to rather than replaces, so an exported value survives
even alongside `--trace`. No model change, nothing committed:

```bash
export JAVA_TOOL_OPTIONS="-Dhttp.proxyHost=127.0.0.1 -Dhttp.proxyPort=8080"
mxcli run --local -p app.mpr
```

Two things to know before committing to this route:

- **HTTPS is the real work.** The proxy must present a certificate the JVM
trusts. WireMock 3.13.2 on Java 21 cannot generate a usable one; use mitmproxy
(which ships a CA you install into the JVM truststore) or supply your own
keystore. Plain `http://` targets need none of this — one more reason to have
the mock on loopback HTTP.
- **Loopback is not proxied.** `127.0.0.1` sits in the runtime's
`http.nonProxyHosts`, so app→mock traffic on loopback works *inside* a proxied
container without any exemption of your own. It also means a proxy on
`127.0.0.1` will not intercept loopback calls — that is not a bug to hunt.

For **consumed OData** services specifically there is a fourth route that needs
no JVM flags: `System.ConsumedODataConfiguration` carries `ProxyConfiguration`,
`ProxyHost` and `ProxyPort` as data (see `system-module.md`), so the proxy can be
set per service at runtime.

## Verify the mock before blaming Mendix

Always prove the endpoint from the shell first. A Mendix error message cannot
distinguish "the mock is not running" from "the mapping is wrong".

```bash
curl -sS -i http://127.0.0.1:4020/rates # 200 + the example payload?
curl -sS -i -H 'Prefer: code=404' http://127.0.0.1:4020/rates
```

Then, and only then, run the microflow and check the payload actually reached it
(`mxcli oql`, or the runtime log under `mxcli run --local`).

## Failure modes, symptoms first

| Symptom | Cause | Fix |
|---|---|---|
| Every path 404s, server looks fine | The client address includes the contract's base path | Address Prism at the root: `http://127.0.0.1:4020` |
| Import produced a client with no `BaseUrl` | `servers[0].url` is relative — mxcli warned and skipped it | Make it absolute, or pass `BaseUrl:` explicitly |
| Prism never finishes starting | Vendor contract is tens of MB | Cut the paths you need into a small contract |
| Mock returns 401 | The contract declares `security`; the call sent no credentials | Add the header, or drop `security` from your cut contract |
| Calls still reach the real API | Proxy properties not applied, or the target is loopback (never proxied) | Check the JVM args; prefer the constant route over a proxy |
| Endpoint swapped but the app disagrees | An override on a different layer wins | `mxcli constant list -p app.mpr` — it names the layer |
| Works with `example` values, fails in CI | The mapping depends on one fixed payload | Run `prism mock -d` locally and fix the mapping |

## Related Skills

- `rest-client.md` — the three ways to call a REST API; where the contract goes once you have one
- `rest-call-from-json.md` — JSON structure → entities → import mapping → `REST CALL`
- `test-app.md` — browser verification; a REST app's prerequisite is a reachable endpoint
- `test-microflows.md` — running a suite; `--constant` points it at the mock
- `run-local.md` — `mxcli run --local`, the warm loop the mock plugs into
7 changes: 7 additions & 0 deletions .claude/skills/mendix/overview-pages.md
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,13 @@ navigationlist widgetName {
- `action: microflow Module.MicroflowName(Param: $value)` - Call microflow with parameters
- `action: show_page Module.PageName` - Navigate to page
- `action: show_page Module.PageName(Param: $value)` - Navigate with parameters
- **A `show_page` argument must be the context object.** Mendix takes the page
argument from the enclosing data widget, so the only spellings that mean
anything are `$currentObject` or the name of the variable that widget is bound
to (`datasource: $Customer` → `(Customer: $Customer)` is fine). Naming any other
variable is refused as **MDL-PAGEARG01** — it used to be accepted and silently
opened the page with the context object anyway. To open a page with something
else, call a microflow that shows it.

## Handling Circular Dependencies

Expand Down
11 changes: 10 additions & 1 deletion .claude/skills/mendix/rest-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Mendix offers three ways to call REST APIs from microflows. Choose based on the

Both REST Client approaches can be combined with **Data Transformers** (Mendix 11.9+) and **Import/Export Mappings** to map between JSON and entities.

No API to call against yet — or one you would rather not depend on while building? [mock-rest-apis.md](mock-rest-apis.md) covers standing up an endpoint you control and pointing the app at it.

---

## Approach 0: OpenAPI Import (Fastest)
Expand Down Expand Up @@ -44,7 +46,14 @@ This generates:
- Basic auth if the spec declares it at the top level
- The spec stored inside the document for Studio Pro parity

`BaseUrl` is optional. When omitted, `servers[0].url` from the spec is used. When provided, it overrides that value — useful when the spec points at production but you need to import against staging or a different version.
`BaseUrl` is optional. When omitted, `servers[0].url` from the spec is used — but only if it is
**absolute**. A relative server URL (`/api/v3`) cannot be a `BaseUrl`: the import warns
(*"server URL … is relative and cannot be used as BaseUrl"*) and leaves the client without one,
which fails at call time rather than at import time. Set `BaseUrl` explicitly in that case.

When provided, `BaseUrl` overrides the spec's value — useful when the spec points at production
but you need to import against staging, a different version, or a local mock
(see [mock-rest-apis.md](mock-rest-apis.md)).

**Preview without writing:**
```sql
Expand Down
2 changes: 2 additions & 0 deletions .claude/skills/mendix/test-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ The devcontainer created by `mxcli init` installs:
- **Chromium (headless shell)** — installed via `@playwright/cli`'s **bundled** `playwright-core`, into a shared `PLAYWRIGHT_BROWSERS_PATH`, and exposed at the stable path `/usr/local/bin/mx-headless-shell`. The generated `.playwright/cli.config.json` pins `executablePath` to that symlink.
- **Docker-in-Docker** — Mendix + PostgreSQL running via `mxcli docker run`

If the app calls an external REST API, that endpoint is a prerequisite too — a verification run that depends on a live third party is not repeatable. See [mock-rest-apis.md](mock-rest-apis.md).

The app must be running before verification:

```bash
Expand Down
Loading
Loading