diff --git a/CHANGELOG.md b/CHANGELOG.md index 155c614b..36421082 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are. Named m ## Unreleased +- learn(rest-between-mendix-apps): **new on-demand skill for two or more Mendix apps you build that read each other over REST (app-to-app, microservices).** The contract decisions to write down before the first script (pull vs push, versioned path, key + version rule, cursor paging, `since` with overlap, error shape incl. the runtime's 560, auth in the operation microflow as of mxcli v0.24 per BUG-152, fixed ports, one owner per fact), 17 publisher and consumer rules for honest "as of"/stale/"never read" states, and a six-rung cross-app test ladder with a per-change/slice/release cadence; a signal-stopped test run is BUG-157. The 26-row failure register behind it is `process/rest-between-mendix-apps-register-2026-10.md`. Routed ondemand for architect, mdl, test and review at stages 3–6. Field evidence: three demo apps, mxcli v0.24–v0.26, Mendix 11.15, 2026-10-07 to 2026-10-12, domain names generalised — MendixMau - fix(docs-numbering): **the pre-commit doc numbering check no longer fails every merge commit.** A merge stages all the other side's files, so the check examined docs the merge never touched and tripped on the existing "15-step" prose in `skills/source-triage.md:240` (plus two siblings that fail `--all`), which sent agents to `--no-verify`. During a merge it now examines only files that differ from both parents. The doc prose is left alone (an anecdote, not a drift). `tests/wave2/test-docs-numbering.sh` T11 fails on master's script. — agent report of `--no-verify` merges - fix(journey-runner): **a journey file without a string `id` now warns naming the file and uses its basename as the id, so captures are named `-stepN.png` instead of `undefined-stepN.png`; two journeys ending up with the same id (explicit, or a basename that collides with an explicit id) are refused with exit 2 before the browser opens, naming both files.** The only new refusal is a duplicate id; existing id-less journey files keep running. `journey-examples.md` field table updated ([#383](https://github.com/MendixMau/mxcli-project-toolkit/issues/383)) — joshuamoesa - fix(lint-gate): **a ratchet rise now lists the findings that are NEW, not three arbitrary members of the rule.** The baseline keeps a multiset of findings (rule, module, document, message) next to the counts; on a rise the gate prints the surplus per rule, up to three per rule (the rest in `.claude/loop/lint-last.json`), labelled as new or changed findings next to the net rise. An old counts-only baseline falls back to the first three and says they are not necessarily new. Not done here: pre-exec snapshot attribution, a per-slice growth cap, a slice-close lint check (the rest of [#239](https://github.com/MendixMau/mxcli-project-toolkit/issues/239)). Test: `tests/wave2/test-lint-gate-new-findings.sh` — MendixMau (#239) diff --git a/README.md b/README.md index f7f6f179..6b5bf96f 100644 --- a/README.md +++ b/README.md @@ -653,6 +653,7 @@ Every mxcli project has a `.ai-context/skills/` directory (bundled by `mxcli ini |---|---| | Wiring a service seam between two Mendix apps — one app's OData or REST consumed by another, replacing a stub by an external entity, joining data that lives in the other app, or choosing keys and auth for an app-to-app service | `skills/mendix-service-seam.md` | | Building a REST integration (consumed or published) for the first time on a project — the checks that avoid a rebuild after the first live call | `skills/rest-integration-first-time-right.md` | +| App-to-app REST or microservices: two or more Mendix apps you build that read each other over REST — before the contract, before the first consumer, and before calling an API tested: pull vs push, key+version rule, auth in the operation microflow (BUG-152), honest last-success/stale/"as of", one owner per fact, and the six-rung cross-app test ladder with its per-change/slice/release cadence | `skills/rest-between-mendix-apps.md` | | Adding a constant, installing a marketplace module, deploying to a new environment, or needing a Mendix PAT — where an encryption key, API credential, endpoint or access token gets its value, why a free node has only one channel, and why a token missing from the files you grepped is not missing | `skills/learned-constants-and-secrets.md` | **Verify — does it work** diff --git a/ROUTING.md b/ROUTING.md index 22b4b0c3..5b110dd8 100644 --- a/ROUTING.md +++ b/ROUTING.md @@ -197,6 +197,7 @@ picks the row up. That is the whole procedure — there is no second list to rem |---|---|---|---|---| | Wiring a service seam between two Mendix apps — one app's OData or REST consumed by another, replacing a stub by an external entity, joining data that lives in the other app, or choosing keys and auth for an app-to-app service | `skills/mendix-service-seam.md` | lead,architect,mdl | 3,5 | ondemand | | Building a REST integration (consumed or published) for the first time on a project — the checks that avoid a rebuild after the first live call | `skills/rest-integration-first-time-right.md` | mdl | 4,5 | ondemand | +| App-to-app REST or microservices: two or more Mendix apps you build that read each other over REST — before the contract, before the first consumer, and before calling an API tested: pull vs push, key+version rule, auth in the operation microflow (BUG-152), honest last-success/stale/"as of", one owner per fact, and the six-rung cross-app test ladder with its per-change/slice/release cadence | `skills/rest-between-mendix-apps.md` | architect,mdl,test,review | 3,4,5,6 | ondemand | | Adding a constant, installing a marketplace module, deploying to a new environment, or needing a Mendix PAT — where an encryption key, API credential, endpoint or access token gets its value, why a free node has only one channel, and why a token missing from the files you grepped is not missing | `skills/learned-constants-and-secrets.md` | mdl,architect,gate | 4,5,6 | ondemand | #### Build · Security diff --git a/agents/architect-agent.md b/agents/architect-agent.md index 2bfd0c0d..576772fe 100644 --- a/agents/architect-agent.md +++ b/agents/architect-agent.md @@ -79,6 +79,7 @@ This table is your whole list. The baseline table in the project's CLAUDE.local. | `skills/agent-permission-friction.md` | Any refused, denied or blocked command | | `project-bin/build-plan-status.sh` | After marking a module done, or any time "how much is built vs proven" is asked | | `skills/workflow-structure-rules.md` | Designing or reviewing a Workflow's SHAPE before or after the MDL | +| `skills/rest-between-mendix-apps.md` | App-to-app REST or microservices: two or more Mendix apps you build that read each other over REST | | `skills/mendix-epics-api.md` | Working with the Mendix Epics board programmatically | | `skills/upstream-feedback.md` | About to open an issue, PR or discussion against mxcli or the toolkit | | `skills/doctor-triage.md` | doctor.sh reports FAIL or WARN, or someone asks whether a red setup line blocks them | diff --git a/agents/mdl-agent.md b/agents/mdl-agent.md index 3d348cd7..cb1bb928 100644 --- a/agents/mdl-agent.md +++ b/agents/mdl-agent.md @@ -92,6 +92,7 @@ This table is your whole list. The baseline table in the project's CLAUDE.local. | `skills/learned-workflow-patterns.md` | Writing or debugging a Mendix native Workflow (CREATE WORKFLOW/USER TASK/OUTCOMES) | | `skills/workflow-structure-rules.md` | Designing or reviewing a Workflow's SHAPE before or after the MDL | | `skills/rest-integration-first-time-right.md` | Building a REST integration (consumed or published) for the first time on a project | +| `skills/rest-between-mendix-apps.md` | App-to-app REST or microservices: two or more Mendix apps you build that read each other over REST | | `skills/bug-submission-checklist.md` | Preparing an mxcli/Studio Pro bug for submission | | `skills/upstream-feedback.md` | About to open an issue, PR or discussion against mxcli or the toolkit | | `skills/empty-widget-triage.md` | A page/grid/list/combobox renders or shows empty ('0 to 0 of 0', blank cells, zero rows, zero options) during UI review or an e2e run | diff --git a/agents/review-agent.md b/agents/review-agent.md index 22c8f86b..f8590697 100644 --- a/agents/review-agent.md +++ b/agents/review-agent.md @@ -89,6 +89,7 @@ This table is your whole list. The baseline table in the project's CLAUDE.local. | `skills/journey-examples.md` | Writing an actual .journey.json | | `skills/wiring-sweep.md` | Every module before it is called done; buttons, links or actions that do nothing when clicked | | `skills/workflow-structure-rules.md` | Designing or reviewing a Workflow's SHAPE before or after the MDL | +| `skills/rest-between-mendix-apps.md` | App-to-app REST or microservices: two or more Mendix apps you build that read each other over REST | | `skills/bug-submission-checklist.md` | Preparing an mxcli/Studio Pro bug for submission | | `skills/upstream-feedback.md` | About to open an issue, PR or discussion against mxcli or the toolkit | | `skills/empty-widget-triage.md` | A page/grid/list/combobox renders or shows empty ('0 to 0 of 0', blank cells, zero rows, zero options) during UI review or an e2e run | diff --git a/agents/test-agent.md b/agents/test-agent.md index 60c6f1df..0a552b8d 100644 --- a/agents/test-agent.md +++ b/agents/test-agent.md @@ -68,6 +68,7 @@ This table is your whole list. The baseline table in the project's CLAUDE.local. | `skills/improvement-register.md` | Any review pass that runs more than once | | `skills/journey-examples.md` | Writing an actual .journey.json | | `skills/wiring-sweep.md` | Every module before it is called done; buttons, links or actions that do nothing when clicked | +| `skills/rest-between-mendix-apps.md` | App-to-app REST or microservices: two or more Mendix apps you build that read each other over REST | | `skills/upstream-feedback.md` | About to open an issue, PR or discussion against mxcli or the toolkit | | `skills/empty-widget-triage.md` | A page/grid/list/combobox renders or shows empty ('0 to 0 of 0', blank cells, zero rows, zero options) during UI review or an e2e run | | `skills/doctor-triage.md` | doctor.sh reports FAIL or WARN, or someone asks whether a red setup line blocks them | diff --git a/bin/gate-check.sh b/bin/gate-check.sh index 2a79dbcc..233dc15e 100755 --- a/bin/gate-check.sh +++ b/bin/gate-check.sh @@ -1769,10 +1769,10 @@ stage_protocol_paths() { 0) echo "skills/interview-protocol.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-scope.md skills/source-triage.md skills/image-transcription.md skills/small-project-tier.md skills/existing-app-change.md skills/decompose-app.md skills/app-analysis.md skills/layering-review.md skills/module-dependency-review.md skills/microflow-loop-antipatterns.md skills/assess-migration.md skills/migration-pipeline.md skills/rehost-app.md skills/migrate-general.md skills/migrate-outsystems.md skills/source-os11.md skills/os-xml-schema.md skills/source-node-express-react.md skills/document-discovery.md skills/extractor-quality-loop.md skills/qa-loop-goal-pattern.md skills/mendix-epics-api.md skills/corpus-extraction-integrity.md skills/gate-check-file-locations.md" ;; 1) echo "skills/interview-protocol.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-extraction.md skills/dispatching.md skills/image-transcription.md skills/small-project-tier.md skills/migration-pipeline.md skills/rehost-app.md skills/source-os11.md skills/os-xml-schema.md skills/source-node-express-react.md skills/document-discovery.md skills/extractor-quality-loop.md skills/kb-generation.md skills/corpus-extraction-integrity.md" ;; 2) echo "skills/interview-protocol.md skills/change-intake.md skills/change-intake-detail.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-brd.md skills/checkpoints/checkpoint-architecture.md skills/dispatching.md skills/image-transcription.md skills/small-project-tier.md skills/kb-generation.md skills/brd-generation.md skills/brd-validation.md" ;; - 3) echo "skills/interview-protocol.md skills/change-intake.md skills/change-intake-detail.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-design.md skills/dispatching.md skills/small-project-tier.md skills/mendix-best-practices-index.md skills/decompose-app.md skills/mendix-service-seam.md skills/layering-review.md skills/architecture-blueprint.md skills/modularize-domain.md skills/design-artifacts.md skills/interaction-model.md skills/brd-to-build-plan.md skills/workflow-structure-rules.md skills/learned-mdl-cannot-express.md" ;; - 4) echo "skills/interview-protocol.md skills/change-intake.md skills/change-intake-detail.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-build.md skills/agent-roles.md skills/dispatching.md skills/small-project-tier.md skills/module-brief.md skills/module-folder-convention.md skills/brd-to-build-plan.md skills/coverage-ledger.md skills/workflow-structure-rules.md skills/rest-integration-first-time-right.md skills/learned-constants-and-secrets.md" ;; - 5|build-ready) echo "skills/interview-protocol.md skills/change-intake.md skills/change-intake-detail.md skills/grill-mode.md skills/agent-roles.md skills/dispatching.md skills/module-brief.md skills/learned-mdl-preflight.md skills/module-folder-convention.md skills/learned-microflow-patterns.md skills/microflow-preflight.md skills/mendix-best-practices-index.md skills/ui-preflight-pages.md skills/design-spacing.md skills/learned-stylegallery.md skills/ui-loop.md skills/module-review.md skills/testing-shape.md skills/decompose-app.md skills/mendix-service-seam.md skills/microflow-loop-antipatterns.md skills/rehost-app.md skills/iterative-build-loop.md skills/mdl-cookbook-microflows.md skills/build/mdl/oneshot-mdl-method.md skills/learned-page-patterns.md skills/oneshot-page-structure-patterns.md skills/mendix-agents.md skills/mendix-agent-ui.md skills/mendix-agent-setup.md skills/fixture-seeding.md skills/journey-proof.md skills/unhappy-path-testing.md skills/monkey-test.md skills/e2e-locators-that-lie.md skills/report-schema.md skills/harness-architecture.md skills/process-coherence-pass.md skills/lint-that-actually-runs.md skills/improvement-register.md skills/journey-examples.md skills/wiring-sweep.md skills/learned-workflow-patterns.md skills/workflow-structure-rules.md skills/rest-integration-first-time-right.md skills/bug-submission-checklist.md skills/empty-widget-triage.md skills/learned-sidebar-collapse-icons.md skills/learned-popup-navigation.md skills/learned-datagrid-customcontent-binding.md skills/learned-popup-feedback-pattern.md skills/learned-mdl-cannot-express.md skills/learned-css-that-never-applied.md skills/learned-detection-gaps.md skills/learned-dg2-patterns.md skills/learned-file-upload-widget.md skills/security-is-not-a-later-script.md skills/learned-local-db-confusion.md skills/full-harness-audit.md skills/test-result-audit.md skills/finding-disposition.md skills/preview-over-hub-tunnel.md skills/walking-skeleton.md skills/platform-link.md skills/teamserver-alignment.md skills/learned-constants-and-secrets.md" ;; - 6) echo "skills/interview-protocol.md skills/change-intake.md skills/change-intake-detail.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-cutover.md skills/dispatching.md skills/mendix-best-practices-index.md skills/module-review.md skills/testing-shape.md skills/existing-app-assurance.md skills/decompose-app.md skills/app-analysis.md skills/module-dependency-review.md skills/microflow-loop-antipatterns.md skills/rehost-app.md skills/qa-loop-goal-pattern.md skills/mendix-agent-setup.md skills/e2e-harness-base.md skills/learned-db-assertions.md skills/fixture-seeding.md skills/journey-proof.md skills/unhappy-path-testing.md skills/monkey-test.md skills/e2e-locators-that-lie.md skills/learned-skill-ux-audit.md skills/learned-skill-scope-delta.md skills/report-schema.md skills/harness-architecture.md skills/process-coherence-pass.md skills/e2e-evidence-report.md skills/record-demo-video.md skills/share-demo-package.md skills/lint-that-actually-runs.md skills/improvement-register.md skills/journey-examples.md skills/wiring-sweep.md skills/workflow-structure-rules.md skills/bug-submission-checklist.md skills/empty-widget-triage.md skills/anonymize-client-app-for-demo.md skills/learned-css-that-never-applied.md skills/learned-detection-gaps.md skills/learned-local-db-confusion.md skills/full-harness-audit.md skills/test-result-audit.md skills/finding-disposition.md skills/handoff-to-studio-pro.md skills/preview-over-hub-tunnel.md skills/platform-link.md skills/teamserver-alignment.md skills/learned-constants-and-secrets.md" ;; + 3) echo "skills/interview-protocol.md skills/change-intake.md skills/change-intake-detail.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-design.md skills/dispatching.md skills/small-project-tier.md skills/mendix-best-practices-index.md skills/decompose-app.md skills/mendix-service-seam.md skills/layering-review.md skills/architecture-blueprint.md skills/modularize-domain.md skills/design-artifacts.md skills/interaction-model.md skills/brd-to-build-plan.md skills/workflow-structure-rules.md skills/rest-between-mendix-apps.md skills/learned-mdl-cannot-express.md" ;; + 4) echo "skills/interview-protocol.md skills/change-intake.md skills/change-intake-detail.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-build.md skills/agent-roles.md skills/dispatching.md skills/small-project-tier.md skills/module-brief.md skills/module-folder-convention.md skills/brd-to-build-plan.md skills/coverage-ledger.md skills/workflow-structure-rules.md skills/rest-integration-first-time-right.md skills/rest-between-mendix-apps.md skills/learned-constants-and-secrets.md" ;; + 5|build-ready) echo "skills/interview-protocol.md skills/change-intake.md skills/change-intake-detail.md skills/grill-mode.md skills/agent-roles.md skills/dispatching.md skills/module-brief.md skills/learned-mdl-preflight.md skills/module-folder-convention.md skills/learned-microflow-patterns.md skills/microflow-preflight.md skills/mendix-best-practices-index.md skills/ui-preflight-pages.md skills/design-spacing.md skills/learned-stylegallery.md skills/ui-loop.md skills/module-review.md skills/testing-shape.md skills/decompose-app.md skills/mendix-service-seam.md skills/microflow-loop-antipatterns.md skills/rehost-app.md skills/iterative-build-loop.md skills/mdl-cookbook-microflows.md skills/build/mdl/oneshot-mdl-method.md skills/learned-page-patterns.md skills/oneshot-page-structure-patterns.md skills/mendix-agents.md skills/mendix-agent-ui.md skills/mendix-agent-setup.md skills/fixture-seeding.md skills/journey-proof.md skills/unhappy-path-testing.md skills/monkey-test.md skills/e2e-locators-that-lie.md skills/report-schema.md skills/harness-architecture.md skills/process-coherence-pass.md skills/lint-that-actually-runs.md skills/improvement-register.md skills/journey-examples.md skills/wiring-sweep.md skills/learned-workflow-patterns.md skills/workflow-structure-rules.md skills/rest-integration-first-time-right.md skills/rest-between-mendix-apps.md skills/bug-submission-checklist.md skills/empty-widget-triage.md skills/learned-sidebar-collapse-icons.md skills/learned-popup-navigation.md skills/learned-datagrid-customcontent-binding.md skills/learned-popup-feedback-pattern.md skills/learned-mdl-cannot-express.md skills/learned-css-that-never-applied.md skills/learned-detection-gaps.md skills/learned-dg2-patterns.md skills/learned-file-upload-widget.md skills/security-is-not-a-later-script.md skills/learned-local-db-confusion.md skills/full-harness-audit.md skills/test-result-audit.md skills/finding-disposition.md skills/preview-over-hub-tunnel.md skills/walking-skeleton.md skills/platform-link.md skills/teamserver-alignment.md skills/learned-constants-and-secrets.md" ;; + 6) echo "skills/interview-protocol.md skills/change-intake.md skills/change-intake-detail.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-cutover.md skills/dispatching.md skills/mendix-best-practices-index.md skills/module-review.md skills/testing-shape.md skills/existing-app-assurance.md skills/decompose-app.md skills/app-analysis.md skills/module-dependency-review.md skills/microflow-loop-antipatterns.md skills/rehost-app.md skills/qa-loop-goal-pattern.md skills/mendix-agent-setup.md skills/e2e-harness-base.md skills/learned-db-assertions.md skills/fixture-seeding.md skills/journey-proof.md skills/unhappy-path-testing.md skills/monkey-test.md skills/e2e-locators-that-lie.md skills/learned-skill-ux-audit.md skills/learned-skill-scope-delta.md skills/report-schema.md skills/harness-architecture.md skills/process-coherence-pass.md skills/e2e-evidence-report.md skills/record-demo-video.md skills/share-demo-package.md skills/lint-that-actually-runs.md skills/improvement-register.md skills/journey-examples.md skills/wiring-sweep.md skills/workflow-structure-rules.md skills/rest-between-mendix-apps.md skills/bug-submission-checklist.md skills/empty-widget-triage.md skills/anonymize-client-app-for-demo.md skills/learned-css-that-never-applied.md skills/learned-detection-gaps.md skills/learned-local-db-confusion.md skills/full-harness-audit.md skills/test-result-audit.md skills/finding-disposition.md skills/handoff-to-studio-pro.md skills/preview-over-hub-tunnel.md skills/platform-link.md skills/teamserver-alignment.md skills/learned-constants-and-secrets.md" ;; 7) echo "skills/interview-protocol.md skills/change-intake.md skills/change-intake-detail.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-cutover.md skills/close-the-loop.md skills/share-demo-package.md skills/handoff-to-studio-pro.md skills/platform-link.md skills/teamserver-alignment.md skills/deploy-to-sandbox.md" ;; *) echo "" ;; # diff --git a/bin/lib/skill-routing.tsv b/bin/lib/skill-routing.tsv index ca7d72ec..c425d8e9 100644 --- a/bin/lib/skill-routing.tsv +++ b/bin/lib/skill-routing.tsv @@ -198,6 +198,7 @@ wiring-sweep skills/wiring-sweep.md Every module before it is called done; butto learned-workflow-patterns skills/learned-workflow-patterns.md Writing or debugging a Mendix native Workflow (CREATE WORKFLOW/USER TASK/OUTCOMES) — syntax, the 11 workflow microflow statements, DECISION vs CALL MICROFLOW, and the two corruption classes (binary-version $Type, and create-before-reference) mdl 5 ondemand build/workflow workflow-structure-rules skills/workflow-structure-rules.md Designing or reviewing a Workflow's SHAPE before or after the MDL — where a path may end, boundary event vs event sub-process, parallel-split limits, outcome minimums, targeting from the sentence, multi-user decision methods, which edits break running instances; and any CE6689/CE1844/CE1845/MW0012 after a clean mxcli check architect,mdl,review 3,4,5,6 ondemand build/workflow rest-integration-first-time-right skills/rest-integration-first-time-right.md Building a REST integration (consumed or published) for the first time on a project — the checks that avoid a rebuild after the first live call mdl 4,5 ondemand build/integration +rest-between-mendix-apps skills/rest-between-mendix-apps.md App-to-app REST or microservices: two or more Mendix apps you build that read each other over REST — before the contract, before the first consumer, and before calling an API tested: pull vs push, key+version rule, auth in the operation microflow (BUG-152), honest last-success/stale/"as of", one owner per fact, and the six-rung cross-app test ladder with its per-change/slice/release cadence architect,mdl,test,review 3,4,5,6 ondemand build/integration mendix-epics-api skills/mendix-epics-api.md Working with the Mendix Epics board programmatically — creating/reading stories and epics, updating workflow state, or integrating BRDs with the portal ba,architect P,0 ondemand reference bug-submission-checklist skills/bug-submission-checklist.md 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 mdl,gate,review 5,6 ondemand diagnose upstream-feedback skills/upstream-feedback.md About to open an issue, PR or discussion against mxcli or the toolkit — before drafting, choosing which repo and vehicle it belongs to all - ondemand diagnose diff --git a/process/rest-between-mendix-apps-register-2026-10.md b/process/rest-between-mendix-apps-register-2026-10.md new file mode 100644 index 00000000..c9d39337 --- /dev/null +++ b/process/rest-between-mendix-apps-register-2026-10.md @@ -0,0 +1,49 @@ +# REST between Mendix apps: failure register, 2026-10 + +The evidence behind `skills/rest-between-mendix-apps.md`. Three demo apps built with mxcli +v0.24 to v0.26 on Mendix 11.15, 2026-10-07 to 2026-10-12: an **order app**, a **stock app** and a +**customer app**, with nine published APIs and five consumer directions. Domain names in this file +and in the skill are generic placeholders, not the apps' real domain. + +"Caught by" is the rung of the skill's test ladder (§3) that found the failure. "Would have" is the +cheapest rung that could have. Source: **F** = a FINDINGS line in one of the three apps; **D** = +another project document (a proof file or the UX scan of 2026-10-12). + +| # | Failure | Caught by | Cheapest rung that would have | Src | +|---|---|---|---|---| +| 1 | Unset String query parameter arrives as `''`, rejected as invalid (7 of 29 → 29 of 29) | 1 | 1 | F | +| 2 | Per-test rollback removed a seed the later tests needed | 1 | 1 | F | +| 3 | Published REST has no auth clause in MDL; an auth property is ignored (BUG-152) | 4 | 4 | F | +| 4 | Malformed cursor gets runtime 560 before the microflow; the mock answered 200 | 3 | 3 | F | +| 5 | Mock rejected empty `limit=`; the publisher treats it as not given | 3 | 3 | F | +| 6 | Consumer never pruned rows the publisher stopped serving | the publisher team's confirmation request (cross-app, by hand) | 1b | F | +| 7 | Demo reset left stored copies hanging off recreated records | 6 (journey) | 1b | F | +| 8 | Non-2xx handler lost the status (401 logged as 0) and rolled back the endpoint row | 2 | 2 | D | +| 9 | Cache columns 30 characters, live feed sends 40 | 2, run against the live publisher instead of the mock | 3 | D | +| 10 | `LastError = ''` matched `!= empty`; healthy feeds shown as unreachable | live UI review | 1b | F | +| 11 | Producer hardcoded version and state; `since` missed the cancellation timestamp; consumer cursor skipped the cancellation | 6 (fresh cross-app story) | 1 | F | +| 12 | Stale mock process on the old port served old records; false test failure | 2 | 2 (reset fixtures first) | F | +| 13 | Two consumer tests pass on the mock and fail on the live publisher | 3 (by accident: run against both) | 3 | F | +| 14 | Consumer fallback seeded "Approved, now" on a failed read; owner holds the order Open | cross-app process trail and UX scan | 5 | F | +| 15 | Same order: 5 support cases in one app, 6 in another | recorded video, UX scan | 5 | F | +| 16 | Status card "Reachable · ok" from a 3-day-old probe while live calls returned 400 | UX scan | 1b (card fed by the call path) | D | +| 17 | "Last success" days old on APIs polled every 5 minutes; no last-error shown (cause not investigated) | UX scan | 1b + a stale marker | D | +| 18 | Eligibility checked once at publish while the other app was down; never retried | live page review | 2 (`down` mode at first call) | F | +| 19 | Field sign in the contract example opposite to what the database stores | building rung 1 | 1 | F | +| 20 | Rows with an empty `updatedAt` never match the cursor | building rung 1 | 1 | F | +| 21 | Date-shaped JSON sample inferred DateTime; String mapping failed mxbuild (CE5015) | exec gate (mxbuild) | exec gate | F | +| 22 | Two apps on one host shared the session cookie; one login signed the other out | 6 | 6 | F | +| 23 | StackOverflowError at startup: a test run stopped by a signal left after-startup on the injected `MxTest` flow, and the next injection chained it to itself (BUG-157) | 1 | — (process rule) | F | +| 24 | A machine-wide exec guard refused one app's exec while another app wrote (fixed by the per-project exec lock, #304; no longer applies) | any exec | — | F | +| 25 | Consumed-test wrapper read only the first verdict | audit of the wrapper | — (read every verdict) | F | +| 26 | Journey order load-bearing: a mock reset in one journey broke the next (4 false failures) | 6 | — (order in the suite) | F | + +**What the register says.** Rungs 1–4 caught 12 of 26; a journey (rung 6) caught 4; a human +looking (page review, UX scan, recorded video, another team's question) caught 7; the exec gate +or an audit of a runner caught 3. Of the 11 found by a journey or a human, 9 had a cheaper rung +that would have caught them, and two of those — the cross-app contradictions, 14 and 15 — needed +the one rung nobody ran: rung 5. + +Row 23 was first recorded as "`test --local` while the same model runs with `--test-endpoint`". +The 2026-10 review matched it to BUG-157, whose mechanism (a signal-stopped run skips the restore) +is the one stated above. diff --git a/skills/rest-between-mendix-apps.md b/skills/rest-between-mendix-apps.md new file mode 100644 index 00000000..ccdbe12b --- /dev/null +++ b/skills/rest-between-mendix-apps.md @@ -0,0 +1,221 @@ +# REST between Mendix apps: contract, consumer honesty, and a test ladder that stays cheap + +**Applies to:** two or more Mendix apps that you build yourself and that read each other's data over +REST ("microservices between Mendix apps"). One app publishes what it owns; another polls it. + +**Purpose:** the decisions to make before the first script, the consumer habits that keep a page +honest about stale data, and how to test across apps without paying for a full multi-app run on +every change. + +**Evidence:** three demo apps built with mxcli v0.24 to v0.26 on Mendix 11.15, October 2026: an +**order app** (publishes `rest/orders/v1` and `rest/announcements/v1`), a **stock app** (polls the +order app, publishes `rest/order-readiness/v1` and two stock feeds) and a **customer app** (publishes +four customer feeds and polls both others). Nine published APIs, five consumer directions. Domain +names are generic placeholders. Every numbered rule in §2 and every result in §3 that is not marked +**(recommendation)** comes from a failure or proof recorded in the apps' FINDINGS files or proof +documents; the 26-row register is `process/rest-between-mendix-apps-register-2026-10.md`. In §1, +a "Why" marked **(design choice)** is reasoning the apps adopted, not a recorded failure. + +**Not this file:** + +| For | Read | +|---|---| +| JSON structure, import mapping, the `all` range flag, a REST-fed DataGrid | `rest-integration-first-time-right.md` | +| Which channel a password or base URL comes from | `learned-constants-and-secrets.md` | +| What "tested" means for one module, and the false-green register | `testing-shape.md` | +| MDL syntax | `./mxcli syntax rest` → `rest.published`, `rest.consumed`, `rest.call`; `./mxcli syntax integration` | +| A stand-in for a third-party API (Prism, proxy) | the mxcli skill `mock-rest-apis` | + +--- + +## 1. Before the first script: write the contract down + +Write the contract **in the publisher's repo** as an OpenAPI file per API, plus one short markdown +file with the decisions. The consumer's team reviews it; nothing is built until both agree. +In the evidence, every API had an OpenAPI file, and the decisions table (pull only, key, version +rule, auth, versioning) was settled before either side wrote MDL. + +Decide these, and write each down: + +| Decision | What the evidence did | Why | +|---|---|---| +| **Pull or push** | Pull only, in every direction. Each app publishes what it owns; the other polls. | No outbox, no retry queue, no inbound write endpoint. An app that is down is simply not polled, and nothing is lost. Push is a valid choice, but it needs all three: an outbox, a retry queue and an inbound write endpoint. **(design choice)** | +| **Versioned path** | `rest//v1`. Additive changes only inside a version; a breaking change is `/v2` beside `/v1`. Every body carries `contractVersion`. | The consumer can test against a `version` mode of its fixture (§3) and refuse a body it does not understand. | +| **Record key and version** | One key per record (for example `ORD-0001/ITEM-B/WH-A`) and an integer version, +1 on every content change. Receivers upsert on the key and ignore a version not higher than the one stored. | Replays, overlapping polls and a restore from backup all land on the same state. | +| **Paging** | Opaque cursor: `nextCursor` from the previous page, `null` on the last page. Cursor = base64 of `updatedAt|key`; order by `updatedAt`, then key. | Stable under inserts **(design choice)**. Recorded: a row with an empty `updatedAt` never matches a cursor, so the seed must set it on every row. | +| **Incremental read** | `since=`; the consumer sends its last fully processed `updatedAt` minus an overlap (2 minutes in the evidence). | The overlap is safe only because of the version rule above. | +| **Correlation** | The caller sends `X-Correlation-Id`; the publisher echoes it in the response header and in every error body, and both sides log it. | One id to grep in two runtime logs. **(design choice)** | +| **Error shape** | `{ "code": "bad_limit", "message": "...", "correlationId": "..." }`. 401 bad or missing credentials (with `WWW-Authenticate`), 403 known account not allowed on this API, 404 unknown key, 422 bad parameter. | One shape for every consumer to parse **(design choice)**. Recorded: the runtime's 560 arrives before your microflow runs (§2, step 3). | +| **Auth** | One service account per direction, named `-reader`, HTTP Basic; HTTPS outside dev. | As of mxcli v0.24 (BUG-152), MDL cannot set the service's own authentication, so the check lives in the operation microflow (§2, step 1). | +| **Ports** | A fixed port per app, written in the plan (in the evidence 8085, 8086, 8087). Never 8081/8091: `mxcli test --local` binds those. | A wrong app answering on a shared port returns 200 with the wrong body. Smoke tests check the body, not only the status. | +| **Who owns each fact** | A table: fact → owning app. The consumer stores a *copy* with an "as of" time and never computes its own version of an owned fact. | §2 has the two cross-app contradictions this rule would have prevented. | +| **Transport dates** | Keep dates as String in the JSON structure and validate them in the microflow; give the snippet a non-date sample. | A date-shaped sample infers DateTime, and a String mapping onto it fails mxbuild with CE5015 (`check` does not see it). | + +Secrets: the reader passwords are **constants with no value in the model**, set per machine +(`.mxcli/constants.json`, never committed) or per environment. An empty value, or a placeholder +such as `__SET_ME__`, closes the account. `learned-constants-and-secrets.md` has the channel rules. + +--- + +## 2. Building it + +### Publisher side + +1. **Authenticate inside the operation microflow.** As of mxcli v0.24, `create published rest + service` has no auth clause, and an `Authentication:` property is silently ignored (BUG-152); + re-probe on a newer binary. Studio Pro can set the published service's own authentication. Done + only in the microflow, the endpoint itself is anonymous at runtime: every request reaches the + microflow with no user, so every operation microflow must run the check, and nothing else stands + in front of it. Read the `Authorization` header, compare against the constant, return 401 with + `WWW-Authenticate` for none or wrong, 403 for a known account on an API it may not read. Then + prove it at runtime (rung 4). Bonus: an inbound call-log row can then name the caller on every + outcome, including 401. +2. **`''` is not `empty` for a String query parameter.** An unset `?limit=` arrives as `''`. Test + `$p != empty and trim($p) != ''`, or every unset parameter is rejected (7 of 29 tests passed + before this fix, 29 of 29 after). +3. **Know which errors you never see.** A malformed parameter such as `cursor=%%%` (invalid + percent-encoding) is rejected by the Mendix runtime with **560** before your microflow runs. A + well-formed bad cursor reaches your code and gets your **422** `bad_cursor`. Put both in the + contract, so the consumer and the mock know the 560 exists. +4. **Every change that alters the payload must bump both `version` and `updatedAt`.** The evidence + shipped a producer with the order version hardcoded to 1 and state hardcoded to `Open`, and a + `since` filter on the order's date only. A cancellation changed neither, so the consumer's + incremental read skipped it and advanced its cursor past it. Rule: list every attribute the + payload projects, and make the `since` XPath cover every timestamp that can change any of them. +5. **One script owns the service and names every resource** — `create or modify published rest + service` replaces the resource list (BUG-152). +6. **Write `contractVersion` and `generatedAt` into every body**, and make the field signs and units + match the contract example (the evidence had a variance value whose sign the database stored the + other way round; the API now writes `0 - VariancePct`). + +### Consumer side + +7. **Keep one endpoint row per upstream API**: `LastSuccessAt`, `LastError`, `LastErrorAt`, + `CredentialsRejected`, cursor, and the source's `generatedAt` of the last good read. +8. **A non-2xx answer raises an error in the runtime.** Handle it **without rollback** and read the + latest HTTP response, or the status is lost: the first handler in the evidence logged a 401 as + 0 "Not reachable" and rolled the endpoint row back. **(recommendation)** A 401 sets + `CredentialsRejected` and stops polling until someone changes the password; a 5xx or timeout + keeps polling. +9. **`LastError = ''` is not `LastError = empty`.** After a good read the code cleared the error to + `''`; the banner's XPath `LastError != empty` matched the empty string, so every healthy feed said + "unreachable". Guard on non-empty string, and test both values (§3, rung 1). +10. **Show "as of" from the source's time, and a stale marker.** The line reads + "Stock readiness as of 2026-10-08 17:17, not reachable" and keeps the last good rows. A first + read that fails says "never read", not "0". A feed polled every 5 minutes whose last success is + days old is stale: say so on the page. **(recommendation)** Stale threshold = 3 × the poll + interval, computed on the page, not stored. +11. **A status card shows the last real call, not a probe.** The evidence had a card saying + "Reachable · ok" from a probe three days old while every live call that day returned 400. Write + `LastSuccessAt` / `LastError` from the production call path, and show both. +12. **Ordering guard.** Ignore a record whose version is not higher than the stored one. A test + pins it with three reads of the same two rows: a higher version stores both, a lower one stores + none, a mixed read stores only the row that is newer. An old message can never + overwrite newer state, and a repeat read changes nothing. +13. **Prune only after a complete good read.** The consumer upserted what the publisher served but + never removed what it stopped serving, so a warehouse stayed on the card after the publisher + dropped it. Delete rows not re-stamped by the latest read, and only when the read had no error + and no next page. A failed or partial read keeps the last known rows. +14. **A cursor can skip; give a full read a button.** After the producer bug in step 4, the + recovery was: scheduled reads stay incremental with overlap, an admin "Read now" does a full, + idempotent read. Keep one. +15. **A check done once at publish time needs a retry.** An eligibility check ran only when the + announcement was published; the other app was down then, and the card said "not reachable" for three + days until a "Re-check" action was added. +16. **One owner per fact; never invent the owner's fact on failure.** Two contradictions reached the + UI: + - a consumer's fallback seeded a record as "Approved, approved now" when its read failed, so the + same order was Approved in one app and Open in the owner; + - two apps counted "support cases for this order" each in their own way (5 in one, 6 in the + other: one counted app-wide cases). + A consumer that has no answer shows "not visible here" or "never read", not a guess. A count of + an owned fact is read from the owner's API, never recomputed. +17. **Size the cache columns from the real feed, not the example.** Item codes up to 40 + characters arrived from the live publisher; the consumer's columns were 30. The mock never sent + one that long. + +--- + +## 3. Testing across apps: the ladder + +Climb from cheap to dear. A rung earns its cost only once the rung below is clean. + +| Rung | What it proves | How (evidence) | Cost measured | +|---|---|---|---| +| **1. Publisher contract tests** | Filters, limits, paging, 404/422, auth rule, version rule — against the publisher's own microflows | `mxcli test tests/api-.test.mdl`: orders 29 cases, announcements 17 | Two measured runs: ~76 s wall for one module's suite, and 9 min 21 s wall for one file whose tests took 848 ms. Wall time is the injection into the `.mpr` and the build, not the tests | +| **1b. Consumer logic tests** | Version rule, prune, "never read" vs "as of", `''` vs empty, feed-status line | Seed endpoint rows and stored copies in the test; call the SUB directly. Feed status: 4 cases (healthy, one failing, none ever read, failure before first success) | Same as rung 1 | +| **2. Consumer vs fixture server** | The consumer's HTTP path: auth, paging, every failure mode, idempotent repeat | One fixture per upstream under `tools/mocks/` (Python or Node, loopback only, fixture password). Modes switched by `GET /mock-admin/mode?mode=normal\|down\|partial\|invalid\|version\|cursor-cycle`; `/mock-admin/stats` counts requests. 6 modes × 3 feeds = 18 cases. Point the consumer's base-URL constant at the fixture for the run only, and restore the constants file on exit | Seconds per case; no publisher running | +| **3. Contract diff, mock vs live publisher** | That the mock still tells the truth | Same requests against the mock and the live publisher; diff keys, shapes and status codes. First run: **9 of 10** — the runtime's 560 on `cursor=%%%` (mock: 200) and an empty `limit=` (mock: 422, publisher: not given) | Minutes; needs the publisher running | +| **4. Live smoke per app** | Auth and routing on the real port | `curl -u` per API: no credentials, wrong password, each reader account. Matrix of 16 of 16 as designed (401/401/200/403 per API), plus `limit=1` then `nextCursor` on every paged API (4 of 4) | Seconds; check the body as well as the status | +| **5. Cross-app consistency** **(recommendation)** | The same key gives the same status and counts in every app | For a handful of keys, read the owner's API and each consumer's stored copy (OQL or its page), compare status, version and counts, print a table with the denominator. Nothing in the evidence did this; a UX scan found the two contradictions in §2 step 16 by eye | Seconds once all apps run | +| **6. Cross-app journeys** | The few flows that truly span apps: publish → poll → act → poll back; upstream down then back | Browser journeys per app, in order: "upstream unreachable after a good read" 26/26; the full cancellation story across three apps | Minutes to hours; run rarely | + +### Cadence + +| When | Run | +|---|---| +| Per change to an API or a consumer | Rung 1 for the touched app (only the touched test file), rung 1b if consumer logic changed, rung 2 for the touched consumer | +| Per slice | Rungs 1–2 in full for the touched apps, rung 3 for every API whose publisher or mock changed, rung 4 for every touched API | +| Per release, or after a contract change | All rungs; rung 5 on a fixed list of keys; rung 6 only for the cross-app journeys | + +### Keeping it cheap + +- **One `mxcli test --local` at a time on the machine, across all apps.** It binds the fixed ports + 8081 and 8091, whichever project runs it. Runners check those ports and refuse when they are + taken; let them, and queue the next app's run behind the current one. +- **`exec.sh` locks per project, not per machine** (#304). Another app's `exec.sh` no longer blocks + yours, so two apps' model writes may run side by side. A raw `mxcli exec` outside `exec.sh` takes + no lock, so keep every model write on `exec.sh`. `mxcli test --attach` counts as a model write + (`testing-shape.md`), so it still needs that app's lock and is not a way to run two tests of one + app in parallel. +- **Never stop a test run with a signal** (BUG-157). `mxcli test` and `run --test-endpoint` point the + after-startup microflow at an injected `MxTest` flow and restore it only on a clean exit. A run + stopped by a kill or a background-task stop leaves the setting there, the next injection chains the + flow to itself, and the app dies at startup with a StackOverflowError. Check the setting before + the next run; BUG-157 has the repair. Never commit while an app started with `--test-endpoint` + runs: it writes an `MxTest` module and an after-startup setting into the model. +- **`@cleanup rollback` is the default.** A seed that later tests use must say `@cleanup none`, and a + cleanup test must remove it; delete by exact key, not by prefix (a prefix cleanup reached demo + data that shared the prefix). +- **Reset every fixture before the suite and name its port in the suite header.** A stale mock + process on the old port served two old records and failed a count; a test that reads a live mock + goes red after any run that changed the mock. +- **An absent upstream is SKIPPED, not passed.** The runner that targets the real publisher reports + SKIPPED when nothing listens on its port, and the wrapper must read **every** verdict, a clean + exit and zero skips — one wrapper read only the first verdict. +- **Two apps on one host share the session cookie.** Give each app its own hostname + (`app-b.localhost`) in browser journeys, or one login signs the other out. +- **Journey order can be load-bearing** when journeys reset a shared mock or need a state another + journey leaves. Write the order in the suite, or make each journey set up its own state. + +--- + +## 4. Definition of done for one API + +| Check | Evidence to show | +|---|---| +| Contract file in the publisher's repo, reviewed by the consumer | the file path | +| Rung 1 and 1b | `N/N` from the test output | +| Rung 2: every fixture mode | `N/N`, modes listed | +| Rung 3: contract diff | the diff file, `x of y` equal, each difference explained | +| Rung 4: status matrix | the curl table | +| Page shows "as of", stale and "never read" states | a screenshot of each, as a real user role | +| Fact ownership row added | the ownership table | + +--- + +## 5. What the evidence says about the ladder + +Of 26 recorded failures, rungs 1–4 caught 12, a journey 4, a human looking 7, the exec gate or a +runner audit 3. Of the 11 found by a journey or a human, 9 had a cheaper rung that would have +caught them; the two cross-app contradictions needed rung 5, which nobody ran. The full register, +with the catching and the cheapest rung per failure, is +`process/rest-between-mendix-apps-register-2026-10.md`. + +## Related + +- `rest-integration-first-time-right.md` — the mapping and page mechanics for one consumer +- `learned-constants-and-secrets.md` — where reader passwords and base URLs live +- `testing-shape.md` — false-green register; test-runtime ports 8081/8091; `--attach` is a model write +- `tool-output-is-not-ground-truth.md` — a smoke test checks the body, not the status code +- `bug-logs/mxcli-bugs.md` BUG-152 — published REST auth, resource list, mapping drops; BUG-157 — a signal-stopped test run breaks startup diff --git a/skills/rest-integration-first-time-right.md b/skills/rest-integration-first-time-right.md index bff7202a..ce369b5c 100644 --- a/skills/rest-integration-first-time-right.md +++ b/skills/rest-integration-first-time-right.md @@ -310,6 +310,8 @@ association direction; all fine, page empty → stale deployment or a different ## Related +- `rest-between-mendix-apps.md` — when both ends are your own Mendix apps: the contract, consumer + honesty (stale, "as of", one owner per fact) and the cross-app test ladder - `learned-mdl-preflight.md` — write-mode choice and the STOP table - `learned-page-patterns.md` — DataGrid-2 filter placement for database-backed grids - `tool-output-is-not-ground-truth.md` — why the runtime test, not `DESCRIBE`, is the oracle