diff --git a/CHANGELOG.md b/CHANGELOG.md index 624c5897..43c5c4f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are. Named m ## Unreleased +- learn(decompose): **three on-demand skills for working from an existing Mendix app (split from #293, 4/6).** `decompose-app.md` covers splitting a monolith into several apps: choosing the cut on business fit and coupling, a gated target architecture, cut order by dependency direction, moving each slice by MPK, ledgering every crossing edge, and reporting the level the slice reached (build-green, seam-live or strangled). `rehost-app.md` covers regenerating a whole `.mpr` into a fresh app with `pipelines/mendix-mpr/` and reading the fidelity ledger; done means build-green with named hand-offs plus journey parity. `mendix-service-seam.md` collects OData/REST patterns between two Mendix apps: keys, machine-role auth measured 200/401/404, error handlers, external-entity limits, stub replacement and cross-app joins. Each pattern is paired with the verbatim failure it prevents. Routing rows are written as trigger conditions; all three are `ondemand`, so the baseline stays at 75,038 words. Citations of unreviewed inbox notes and uncommitted run captures are replaced by the fact and its field run. `architecture-blueprint.md` and `learned-constants-and-secrets.md` gain one pointer each. `decompose-app.md` names `bin/cut-score.sh` and `bin/cut-ledger.sh`, which arrive in their own split PRs. Field runs on public apps: a field-service template (Mendix 9.24) in eight increments up to a strangled slice; a GenAI showcase app and a banking sample for rehost. — MendixMau +- new(pipelines): **`pipelines/mendix-mpr/` — the Rehost extractor for an existing Mendix app (split from #293, 3/6).** `rehost-extract.sh` describes each own module with mxcli, reorders the statements so callees come first, replays them into a fresh target `.mpr` of the same Mendix version (deferring and retrying what `check`/`exec` refuse, never dropping a module), and writes a fidelity ledger with one row per source document and the denominator in its header; `export-module-mpk.py` writes a module package from an MPR v1 app with secrets scrubbed, for Mendix 9 sources whose `mx` cannot export. Code only, no samples; the script now runs without bash-4 features. Added to CLAUDE.md's Pipelines table. Field runs on public apps: a banking sample (Mendix 11.13, 155 documents) rehosted build-green, 154/155 identical; a GenAI showcase app (Mendix 11.12, 2776 documents) landed 36/36 modules but is not build-green (42 × CE1613); module packages from a field-service template (Mendix 9.24) installed into 10.24 and 11.13. macOS and the two-tree layout not verified. — MendixMau - new(pipelines): **`pipelines/mendix-mpr/` — the Rehost extractor for an existing Mendix app (split from #293, 3/6).** `rehost-extract.sh` describes each own module with mxcli, reorders the statements so callees come first, replays them into a fresh target `.mpr` of the same Mendix version (deferring and retrying what `check`/`exec` refuse, never dropping a module), and writes a fidelity ledger with one row per source document and the denominator in its header; `export-module-mpk.py` writes a module package from an MPR v1 app for Mendix 9 sources whose `mx` cannot export; by default it empties demo users, user roles, admin password, per-configuration constant values, database credentials, certificates and navigation profiles in the project documents, while the module's own units (constant default values included) are copied byte for byte; any unknown `--` option is refused (exit 2). Code only, no samples; the script now runs without bash-4 features. After review: module constants' default values still travel in the package by default — the path the field runs installed — and a new `--scrub-constant-defaults` empties them and prints how many (unverified in Studio Pro: no package built with it has been installed and checked yet); the README states exactly what is and is not scrubbed; both scripts refuse source == output/target (exit 2) before writing; the own-module parse moved to `lib/own-modules.awk` and no longer assumes a fixed number of lead lines, with a fixture over verbatim `list modules` captures (`test-rehost-list-modules.sh`, 5 checks; the old script fails 2). Added to CLAUDE.md's Pipelines table. Field runs on public apps: a banking sample (Mendix 11.13, 155 documents) rehosted build-green, 154/155 identical; a GenAI showcase app (Mendix 11.12, 2776 documents) landed 36/36 modules but is not build-green (42 × CE1613); module packages from a field-service template (Mendix 9.24), exported on the default path (constant defaults copied), installed into 10.24 and 11.13. macOS and the two-tree layout not verified. — MendixMau - new(cut-ledger): **`bin/cut-ledger.sh` writes the cut ledger of one decompose slice — one row per crossing edge with its resolution (resolved / stub / handoff / retire / platform) and evidence — and exits 1 while any edge has none** (split from [#293](https://github.com/MendixMau/mxcli-project-toolkit/pull/293), 2/6). Reads the source app's catalog `refs` table (or a `--refs` TSV), a slice file (modules and single documents), first-match rules with mandatory evidence, `mx check` CE1613 names the catalog never recorded (check-only rows), the module exporter's JSON (modules it counts with no ledger row are printed), and verbatim `--extra` rows. #293 shipped it with no fixture; new `tests/wave2/test-cut-ledger.sh` (44 checks) runs on a captured line subset of #293's field run (43 of 346 crossing refs, one key per rule) and reproduces that run's own 57 ledger rows column for column, the pre-fix exporter note, UNRESOLVED → exit 1, refusals → exit 2 (now also an unreadable `--rules`/`--catalog`/`--exporter-json`, which escaped as exceptions with the UNRESOLVED code 1, and a `--refs` line with the wrong field count, which was silently dropped), and `--catalog` at single-tree and `app/` paths. Header corrected to the run's real counts (346 refs → 308 rows; it said 199). — MendixMau (#293 field run on a public template app) - new(cut-score), fix(app-facts): **`bin/cut-score.sh` scores a proposed split of an existing app into several apps; `app-facts.sh` no longer needs the sqlite3 CLI** (split from [#293](https://github.com/MendixMau/mxcli-project-toolkit/pull/293), 1/6). Given `--cut "A,B|C|D"` (or one app per level of `app-layer-map.json`) it lists every module edge that crosses an app boundary from `analysis/app-facts/dependencies.json`, names the lane each ref kind would need (OData external entity, published action or REST, or none), and sums a clean / costly / blocked verdict per partition with the denominator printed ("7 of 7 own edges"); an aggregate over a foreign entity is read from `.mxcli/catalog.db` (single-tree or `app/`), and reads NOT DETECTED with a reason when it cannot be. Adapted to master: the catalog's `build_mode` label is recorded, not tested (#259 — v0.24+ labels a full catalog `source`), and an empty `activities_data` reads NOT DETECTED instead of "no aggregates". `app-facts.sh` runs its guard queries through Python's sqlite3 when the CLI is missing (absent on some cloud containers and always on Git Bash), stripping the `\r` native Windows Python prints. Rows whose aggregate cannot be detected name the actual reason (no catalog / empty table / unreadable). `tests/wave2/test-cut-score.sh` 70/70 on a captured four-module fixture (14 new: label, empty table, row reasons, own modules derived from edges, both layouts); `test-app-report.sh` 109/109; `test-catalog-edge-guard.sh` 22/22, new case forcing the Python path with CRLF output (fails without the `tr`). — MendixMau (#293 field runs on public template apps) diff --git a/README.md b/README.md index 9e76591c..f7f6f179 100644 --- a/README.md +++ b/README.md @@ -536,6 +536,7 @@ Every mxcli project has a `.ai-context/skills/` directory (bundled by `mxcli ini | Generating a new project's CLAUDE.md — baseline routing plus project-specific facts | `skills/bootstrap-project.md` | | Setting up or resuming an mxcli project in a cloud/ephemeral container — the one-time setup order (mxcli download → mxcli init → init-project.sh → sources decision → push) and the commit-and-push loop that survives container reclaim | `skills/cloud-dev-environment.md` | | Changing an EXISTING Mendix app — adding a feature, altering a flow, restructuring a module — when it has no BRDs, no architecture doc and no wireframes: the knowledge base comes from the live model (Path D), stages 2–4 run over what changes plus what it touches, nothing more, and the Track B regression baseline is the precondition; audit-only stays in existing-app-assurance | `skills/existing-app-change.md` | +| Splitting an existing Mendix monolith into several apps — strangler, "move module X into its own app", carving out a slice, microservices from one .mpr; choosing the cut, moving a slice by MPK, accounting for every crossing edge, or reporting the level a slice reached | `skills/decompose-app.md` | | Setting up or wiring a COMPANY BRAIN — the private tier between the toolkit and a project for own skills, conventions, lint rules, MDL snippets and approved MPKs; and deciding whether something goes to the toolkit, the company brain or docs/brain/ | `skills/company-brain.md` | | Cutover and retrospective — promoting proven patterns back into the toolkit | `skills/close-the-loop.md` | | Before citing ANY behavioural claim about the harness, the Mendix runtime or a test tool as evidence, or counting tokens from transcripts — a claim not in the register may not be cited | `skills/measured-claims.md` | @@ -550,6 +551,7 @@ Every mxcli project has a `.ai-context/skills/` directory (bundled by `mxcli ini | Reviewing what the extraction actually produced — the Stage 1 surface, and the file the Stage 1 gate looks for. Renders a code-extracted and a document knowledge base alike, so a requirements-driven project gets the surface too; prints no zero that a second record does not agree with | `bin/extraction-report.sh` | | Assessing or planning a migration up front, before any pipeline is chosen | `skills/assess-migration.md` | | Running the extraction pipeline | `skills/migration-pipeline.md` | +| Rehosting an existing Mendix .mpr into a fresh app — "port this app to a clean Mendix 11 project", re-platforming or rebuilding a whole app from its own model, running pipelines/mendix-mpr/rehost-extract.sh or reading its fidelity ledger | `skills/rehost-app.md` | | Migrating from a stack that has no dedicated pipeline | `skills/migrate-general.md` | | Migrating an OutSystems app | `skills/migrate-outsystems.md` | | Understanding OutSystems 11 source | `skills/source-os11.md` | @@ -649,6 +651,7 @@ Every mxcli project has a `.ai-context/skills/` directory (bundled by `mxcli ini | Task | Skill to load | |---|---| +| 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` | | 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` | diff --git a/ROUTING.md b/ROUTING.md index df4533bc..22b4b0c3 100644 --- a/ROUTING.md +++ b/ROUTING.md @@ -56,6 +56,7 @@ picks the row up. That is the whole procedure — there is no second list to rem | Generating a new project's CLAUDE.md — baseline routing plus project-specific facts | `skills/bootstrap-project.md` | ba | P | ondemand | | Setting up or resuming an mxcli project in a cloud/ephemeral container — the one-time setup order (mxcli download → mxcli init → init-project.sh → sources decision → push) and the commit-and-push loop that survives container reclaim | `skills/cloud-dev-environment.md` | all | P | ondemand | | Changing an EXISTING Mendix app — adding a feature, altering a flow, restructuring a module — when it has no BRDs, no architecture doc and no wireframes: the knowledge base comes from the live model (Path D), stages 2–4 run over what changes plus what it touches, nothing more, and the Track B regression baseline is the precondition; audit-only stays in existing-app-assurance | `skills/existing-app-change.md` | ba,architect | P,0 | ondemand | +| Splitting an existing Mendix monolith into several apps — strangler, "move module X into its own app", carving out a slice, microservices from one .mpr; choosing the cut, moving a slice by MPK, accounting for every crossing edge, or reporting the level a slice reached | `skills/decompose-app.md` | lead,architect,mdl | 0,3,5,6 | ondemand | | Setting up or wiring a COMPANY BRAIN — the private tier between the toolkit and a project for own skills, conventions, lint rules, MDL snippets and approved MPKs; and deciding whether something goes to the toolkit, the company brain or docs/brain/ | `skills/company-brain.md` | all | - | ondemand | | Cutover and retrospective — promoting proven patterns back into the toolkit | `skills/close-the-loop.md` | all | 7 | ondemand | | Before citing ANY behavioural claim about the harness, the Mendix runtime or a test tool as evidence, or counting tokens from transcripts — a claim not in the register may not be cited | `skills/measured-claims.md` | all | - | ondemand | @@ -77,6 +78,7 @@ picks the row up. That is the whole procedure — there is no second list to rem | Reviewing what the extraction actually produced — the Stage 1 surface, and the file the Stage 1 gate looks for. Renders a code-extracted and a document knowledge base alike, so a requirements-driven project gets the surface too; prints no zero that a second record does not agree with | `bin/extraction-report.sh` | ba | 1 | ondemand | | Assessing or planning a migration up front, before any pipeline is chosen | `skills/assess-migration.md` | ba | 0 | ondemand | | Running the extraction pipeline | `skills/migration-pipeline.md` | ba | 0,1 | ondemand | +| Rehosting an existing Mendix .mpr into a fresh app — "port this app to a clean Mendix 11 project", re-platforming or rebuilding a whole app from its own model, running pipelines/mendix-mpr/rehost-extract.sh or reading its fidelity ledger | `skills/rehost-app.md` | lead,ba,mdl | 0,1,5,6 | ondemand | | Migrating from a stack that has no dedicated pipeline | `skills/migrate-general.md` | ba | 0 | ondemand | | Migrating an OutSystems app | `skills/migrate-outsystems.md` | ba | 0 | ondemand | | Understanding OutSystems 11 source | `skills/source-os11.md` | ba | 0,1 | ondemand | @@ -193,6 +195,7 @@ picks the row up. That is the whole procedure — there is no second list to rem | Always relevant for | Load this | Agent(s) | Stage(s) | Tier | |---|---|---|---|---| +| 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 | | 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 | diff --git a/agents/architect-agent.md b/agents/architect-agent.md index 0d4a43cb..2bfd0c0d 100644 --- a/agents/architect-agent.md +++ b/agents/architect-agent.md @@ -58,6 +58,8 @@ This table is your whole list. The baseline table in the project's CLAUDE.local. | `project-bin/check-prototype-links.js` | Before wireframes pass to the build loop, and with --brd before a BRD is signed off: dead #/route links, orphan screens, controls with no data-bind and no data-cut, BRD routes no screen has, screens no use case walks (design-artifacts.md Step 3c, brd-validation.md check 8) | | `skills/cloud-dev-environment.md` | Setting up or resuming an mxcli project in a cloud/ephemeral container | | `skills/existing-app-change.md` | Changing an EXISTING Mendix app | +| `skills/decompose-app.md` | Splitting an existing Mendix monolith into several apps | +| `skills/mendix-service-seam.md` | Wiring a service seam between two Mendix apps | | `project-bin/app-facts.sh` | Collecting the facts an app dossier is written from: forces a full catalog build, module edges through the real module column, strongly connected components, describes every loop-containing microflow and parses the loop bodies; exit 2 on a stale, fast-mode or schema-incomplete catalog, never a verdict | | `skills/app-analysis.md` | Building or refreshing the standing dossier of an EXISTING app before changing it: inventory, module dependency shape, loop risk patterns, each section with a verdict and a fault where nothing was measured (a standing document; nothing else in the toolkit reads it yet) | | `skills/layering-review.md` | Checking whether an EXISTING app's module boundaries still hold: the layer map (bin/app-layer-map.sh) computes the order the modules would stack in and draws only the edges that point back up, so one tangle of N mutually reachable modules becomes a named list of edges with a weight and a ref kind; Stage 3 in existing-app-change mode, and the honest blast radius for a slice | diff --git a/agents/ba-agent.md b/agents/ba-agent.md index 4b77ac71..6bf934d2 100644 --- a/agents/ba-agent.md +++ b/agents/ba-agent.md @@ -75,6 +75,7 @@ This table is your whole list. The baseline table in the project's CLAUDE.local. | `skills/layering-review.md` | Checking whether an EXISTING app's module boundaries still hold: the layer map (bin/app-layer-map.sh) computes the order the modules would stack in and draws only the edges that point back up, so one tangle of N mutually reachable modules becomes a named list of edges with a weight and a ref kind; Stage 3 in existing-app-change mode, and the honest blast radius for a slice | | `skills/assess-migration.md` | Assessing or planning a migration up front, before any pipeline is chosen | | `skills/migration-pipeline.md` | Running the extraction pipeline | +| `skills/rehost-app.md` | Rehosting an existing Mendix .mpr into a fresh app | | `skills/migrate-general.md` | Migrating from a stack that has no dedicated pipeline | | `skills/migrate-outsystems.md` | Migrating an OutSystems app | | `skills/source-os11.md` | Understanding OutSystems 11 source | diff --git a/agents/mdl-agent.md b/agents/mdl-agent.md index 11ac178b..3d348cd7 100644 --- a/agents/mdl-agent.md +++ b/agents/mdl-agent.md @@ -68,7 +68,10 @@ This table is your whole list. The baseline table in the project's CLAUDE.local. | `project-bin/context-pack.sh` | Dispatching a build step to a helper | | `bug-logs/mxcli-bugs.md` | Reading a whole class of tool defects (a retest, a new mxcli release, an audit) | | `skills/cloud-dev-environment.md` | Setting up or resuming an mxcli project in a cloud/ephemeral container | +| `skills/decompose-app.md` | Splitting an existing Mendix monolith into several apps | +| `skills/mendix-service-seam.md` | Wiring a service seam between two Mendix apps | | `skills/microflow-loop-antipatterns.md` | Reading what loop bodies do (LOOP_TQ, deferred commit, nested loop, REST in loop, transaction control per item, scheduled-event reachability) from described MDL; the catalog holds top-level activities only and cannot see inside a loop | +| `skills/rehost-app.md` | Rehosting an existing Mendix .mpr into a fresh app | | `skills/iterative-build-loop.md` | An exec.sh guard refused a write (not a permission prompt), or building a module with mxcli | | `skills/mdl-cookbook-microflows.md` | Writing MDL microflow scripts | | `skills/build/mdl/oneshot-mdl-method.md` | Writing a single MDL script that takes a project from nothing to a working vertical slice | diff --git a/bin/gate-check.sh b/bin/gate-check.sh index 77b06c21..2a79dbcc 100755 --- a/bin/gate-check.sh +++ b/bin/gate-check.sh @@ -1766,13 +1766,13 @@ stage_protocol_paths() { case "$1" in # P) echo "skills/interview-protocol.md skills/grill-mode.md skills/agent-roles.md skills/dispatching.md skills/bootstrap-project.md skills/cloud-dev-environment.md skills/existing-app-change.md skills/app-analysis.md skills/mendix-epics-api.md skills/corpus-extraction-integrity.md skills/platform-link.md" ;; - 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/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/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/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" ;; + 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/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" ;; + 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/microflow-loop-antipatterns.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/app-analysis.md skills/module-dependency-review.md skills/microflow-loop-antipatterns.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" ;; + 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" ;; 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 0ab346d9..ca7d72ec 100644 --- a/bin/lib/skill-routing.tsv +++ b/bin/lib/skill-routing.tsv @@ -128,6 +128,8 @@ bootstrap-project skills/bootstrap-project.md Generating a new project's CLAUDE. cloud-dev-environment skills/cloud-dev-environment.md Setting up or resuming an mxcli project in a cloud/ephemeral container — the one-time setup order (mxcli download → mxcli init → init-project.sh → sources decision → push) and the commit-and-push loop that survives container reclaim all P ondemand spine existing-app-assurance skills/existing-app-assurance.md Auditing or regression/e2e-testing an EXISTING app — no intake, no stages, no gates test,review 6 ondemand verify existing-app-change skills/existing-app-change.md Changing an EXISTING Mendix app — adding a feature, altering a flow, restructuring a module — when it has no BRDs, no architecture doc and no wireframes: the knowledge base comes from the live model (Path D), stages 2–4 run over what changes plus what it touches, nothing more, and the Track B regression baseline is the precondition; audit-only stays in existing-app-assurance ba,architect P,0 ondemand spine +decompose-app skills/decompose-app.md Splitting an existing Mendix monolith into several apps — strangler, "move module X into its own app", carving out a slice, microservices from one .mpr; choosing the cut, moving a slice by MPK, accounting for every crossing edge, or reporting the level a slice reached lead,architect,mdl 0,3,5,6 ondemand spine +mendix-service-seam skills/mendix-service-seam.md 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 lead,architect,mdl 3,5 ondemand build/integration app-facts project-bin/app-facts.sh Collecting the facts an app dossier is written from: forces a full catalog build, module edges through the real module column, strongly connected components, describes every loop-containing microflow and parses the loop bodies; exit 2 on a stale, fast-mode or schema-incomplete catalog, never a verdict architect,review 0,6 ondemand architecture app-analysis skills/app-analysis.md Building or refreshing the standing dossier of an EXISTING app before changing it: inventory, module dependency shape, loop risk patterns, each section with a verdict and a fault where nothing was measured (a standing document; nothing else in the toolkit reads it yet) ba,architect,review P,0,6 ondemand architecture layering-review skills/layering-review.md Checking whether an EXISTING app's module boundaries still hold: the layer map (bin/app-layer-map.sh) computes the order the modules would stack in and draws only the edges that point back up, so one tangle of N mutually reachable modules becomes a named list of edges with a weight and a ref kind; Stage 3 in existing-app-change mode, and the honest blast radius for a slice architect,ba 0,3 ondemand architecture @@ -135,6 +137,7 @@ module-dependency-review skills/module-dependency-review.md Judging module tangl microflow-loop-antipatterns skills/microflow-loop-antipatterns.md Reading what loop bodies do (LOOP_TQ, deferred commit, nested loop, REST in loop, transaction control per item, scheduled-event reachability) from described MDL; the catalog holds top-level activities only and cannot see inside a loop architect,review,mdl 0,5,6 ondemand build/mdl assess-migration skills/assess-migration.md Assessing or planning a migration up front, before any pipeline is chosen ba 0 ondemand source migration-pipeline skills/migration-pipeline.md Running the extraction pipeline ba 0,1 ondemand source +rehost-app skills/rehost-app.md Rehosting an existing Mendix .mpr into a fresh app — "port this app to a clean Mendix 11 project", re-platforming or rebuilding a whole app from its own model, running pipelines/mendix-mpr/rehost-extract.sh or reading its fidelity ledger lead,ba,mdl 0,1,5,6 ondemand source migrate-general skills/migrate-general.md Migrating from a stack that has no dedicated pipeline ba 0 ondemand source migrate-outsystems skills/migrate-outsystems.md Migrating an OutSystems app ba 0 ondemand source source-os11 skills/source-os11.md Understanding OutSystems 11 source ba 0,1 ondemand source diff --git a/pipelines/mendix-mpr/README.md b/pipelines/mendix-mpr/README.md index 2ae74719..6cf4596e 100644 --- a/pipelines/mendix-mpr/README.md +++ b/pipelines/mendix-mpr/README.md @@ -4,6 +4,8 @@ **What it does:** read a model with `mxcli describe`, regenerate it as MDL into a fresh target app, and write a **fidelity ledger** (one row per source document; the denominator in the header) instead of claiming 100 %. +The judgement around it lives in the skills: `skills/rehost-app.md` (when to rehost, the +check/exec loop, reading the ledger, done) and `skills/decompose-app.md` (splitting an app by MPK). This is the only pipeline whose extractor reads a Mendix model. Downstream of the ledger nothing here is Mendix-source-specific: the slice loop of the Decompose track reuses it to move a diff --git a/skills/architecture-blueprint.md b/skills/architecture-blueprint.md index 49c20cb9..68139efc 100644 --- a/skills/architecture-blueprint.md +++ b/skills/architecture-blueprint.md @@ -373,6 +373,8 @@ For every integration point the fit-gap flagged (Step 4) as needing a live conne Record each as a row in `PROJECT.md` (Step 5's register) with its `CONFIRMED`/`ASSUMED` status, not as a separate untracked list — it's a dependency like any other. +An integration between two Mendix apps (one publishes, the other consumes) also needs keys, auth roles, error contract, ownership and external-entity limits decided → `mendix-service-seam.md`. + --- ## Step 7: Render the Checkpoint Surface (`architecture/blueprint.html`) diff --git a/skills/decompose-app.md b/skills/decompose-app.md new file mode 100644 index 00000000..bb93d7f0 --- /dev/null +++ b/skills/decompose-app.md @@ -0,0 +1,238 @@ +# Decompose App — Splitting a Mendix Monolith into Several Apps + +**Applies to:** any mxcli project — an existing Mendix app (9 or later) being split into several +Mendix apps, one slice at a time, with the monolith staying live (strangler). +**Purpose:** Pick cuts that are both a business capability and cheap to wire, move each slice by +module package (MPK), account for every crossing edge, and report the level each slice reached. +This is the Change-an-existing-app mode (`existing-app-change.md`) with a cut-scoring Stage 3 and a +per-slice Stage 5 loop. +**Evidence:** increments run 2026-10-12/13. Increment 1 built `bin/cut-score.sh` on a synthetic +4-module app (Mendix 10.24). Increments 2–8 ran on a public field-service template (Mendix 9.24.2, +MPR v1): increment 2 the read probe and the first cut scoring, 3 a native notes slice by MPK, +4 a first seam probe, 5 an asset/parts web slice, 6 the monolith publishing its core, 7 that +slice strangled out of the monolith (level 3), 8 a dispatch/workforce slice run from this skill. "Increment N" below names those runs; results in +`pipelines/mendix-mpr/README.md` → "Field runs". Their captures are not committed to the toolkit. + +**Not this skill:** regenerating a whole `.mpr` as MDL into a clean app → `rehost-app.md`. +Module boundaries *inside* one app → `modularize-domain.md` / `layering-review.md`. + +--- + +## Stage 0 — Preconditions and scope + +Bound: every line below is a `PROJECT.md` register line, `CONFIRMED` or `ASSUMED`. + +1. **Mendix 9 or later.** Earlier versions are out of scope; the upgrade hop is the customer's job. +2. **Work on a copy.** The original `.mpr` is read-only for the whole decomposition. +3. **A 9.x monolith is converted to 10.24 first** (`mx convert`, on the copy) — a means, not a + deliverable. Why: Mendix refuses a 9.x package into 11.x, so the MPK export always goes through a + 10.24 copy. mxcli ≤ 0.26.0 also cannot create or alter a microflow in a 9.x project, so today the + monolith-side edits run on that copy too; **[fixed after 0.26.0]** (microflow written into 9.24.2, `mx check` 0) + — then monolith-side edits can stay on 9.x. `mxcli run` still fails on 9.x (nightly: the runtime + launcher rejects JDK 17, `Java 11 is required`). Budget the one-time fixes + (marketplace and widget updates, Java API edits): on the field-service template they cost more than the + seam itself (increment 4). +4. **Lanes are OData and REST only.** No Event Broker licence, so Business Events is not a lane; an + edge that is neither OData nor REST blocks. +5. **Scope interview** (`interview-protocol.md`; ask, then stop): why split (team, cadence, licence, + risk); how many apps may exist (each app is a licensed cloud node); may a slice leave Mendix; + is data migration of existing rows in scope (the toolkit has not measured it). + +## Stage 1 — Read and score + +Bound: `analysis/cut-score.json` exists for the chosen cut, and each slice has a written business +reason beside its coupling numbers. + +1. Facts: `project-bin/app-facts.sh` (writes `dependencies.json` and the catalog), then + `bin/app-layer-map.sh`. +2. Score candidate cuts: `bin/cut-score.sh --cut "A,B|C|D"` (read its header for flags). Every cut + on a tangled monolith reads `blocked`; compare crossing refs and blocker rows, not the verdict. + Retire test modules; copy utility modules a slice pulls on. +3. **Choose on business fit AND coupling.** The failure: `NativeNote`, the cheapest cut, became the + first slice and is a poor business slice — 26 behaviour stubs went out (increment 3). The + recipe: build a capability table — modules, entities, roles using it, in/out refs by lane, biggest + no-lane calls, one sentence of business fit — and rank. A slice that is mostly inbound reads + (OData-able) and a plausible separate system of record (Assets & Parts, Insights) beats a cheap + fragment of the core. +4. Known score blind spots (measured on the field-service template, increment 2): `call` mixes server and client-side calls + (a nanoflow/JS-action call lanes *copy*, not OData); `sync` refs never reach + `dependencies.json`, so an entity synced to the device that lands in the other app is an + unseen blocker. Check both by hand. +5. **Native and web slices differ.** A native slice needs a native profile: target the Blank + Native Mobile starter. An offline profile cannot sync an external entity, so data the device + needs from the other app arrives as REST-fed local copies, not OData. + +## Stage 1b — Target architecture (gated) ✋ + +Bound: `architecture/target-architecture.md` holds all eight sections below, its module map states +N/N own modules assigned or retired, and every decision is a `PROJECT.md` line, `CONFIRMED` or +`ASSUMED`. No cut, MPK export or seam work starts before this gate. + +The failure it prevents — seven field-service template increments ran without it, and each of these was decided late: +`NativeNote` was cut on coupling alone, yet notes belong to work orders (238 in-edges stayed +behind); Assets & Parts went build-green with **120 out-stub rows** because the core was not +published first (increment 5; 120 → 32 once it was, increment 6); keys, machine-role auth, key-copy joins, dual writes and OData +page outage behaviour were invented mid-build (`mendix-service-seam.md`). + +Write it with `architecture-blueprint.md` Step 7b §A (single vs multi-app), Step 3 (wiring, here +between apps), Step 6a (roles) and Step 6c (contracts). Sections and their checks: +1. **Target landscape** — the apps, within the Stage 0 app count. +2. **Capability → module → app map** — every own module assigned to an app or `retire`: N/N. +3. **Data ownership** — every entity crossing a cut has exactly one owning app. +4. **Contract per seam** — lane (OData read / REST action / key copy / local owner / hand-off), + key, versioned path (`mendix-service-seam.md` P1, P10, recipe B). +5. **Auth between apps** — machine role now (P2); the token-based target named. +6. **Cut order** — follows dependency direction, core published first (Stage 2 recipes). +7. **Mobile/offline partition** — which app owns the native profile and synced entities. +8. **Non-goals** — e.g. who owns data migration of existing rows. + +Gate: put it to the user per `interview-protocol.md` §3, end the turn, wait. Moved modules need no +wireframes or build plan — the existing app is the spec. Only redesigned parts do (legacy widgets → +React client, new seam services, navigation across apps): `design-artifacts.md`, +`brd-to-build-plan.md`. + +## Stage 2 — Sequence the cuts by dependency direction + +Bound: for each planned slice, every out-edge target is either moving with it, copied, platform, +or already published by the monolith. + +A slice can only go seam-live when what it depends on answers over HTTP (failure: Stage 1b). +Recipe — the Stage 1b cut order picks one: +- **Monolith publishes first.** Before cutting a leaf, publish the core services its out-edges need + (OData for reads, REST for writes) from the 10.24 monolith copy; the slice consumes them. +- **Cut in dependency order.** Cut what the leaf depends on before the leaf, so out-edges point at + an app that already publishes. + +**Pre-check: out-edges no service can carry.** Grep the slice's access rules for XPath paths into +other modules (16 such rows on the field-service template). XPath cannot cross apps, so decide per path before cutting: +local owner entity, REST-fed copy, or hand-off. Joins across apps go through a published mirror +(`mendix-service-seam.md` recipe B). + +## Stage 3 — Move the slice (MPK route) + +Bound: every slice module installed; target `mx check` errors are crossing edges only. + +1. Create the target. Web: `mxcli new --version 11.x`. Native: download the Blank Native + Mobile starter and `mx create-project` with an **absolute** `--output-dir` (relative fails). +2. Export each module from the 10.24 copy: + `python3 -I pipelines/mendix-mpr/export-module-mpk.py --json`. + It keeps unit and entity IDs, scrubs secrets, and refuses MPR v2 sources (read its header). +3. Install **dependencies first**: marketplace modules the slice needs (`marketplace install`), + then copied modules, then the slice: `mxcli marketplace install --file -p `. +4. Mendix 10 targets: `mxcli fix widgets` after page exec. 10.x packages into 11.x: legacy widgets + become CE0582 on the React client with no warning at install. Workaround (increment 5, + 22 widgets): read each legacy widget with `mxcli bson dump` and write its React-client + equivalent (the converter scripts of that run are not in the toolkit); converted pages are + `not-expressible` drift on the fidelity check. +5. Java bodies arrive byte-identical; Mendix 9 API calls are hand-edits + (`pipelines/mendix-mpr/README.md` → "The MPK route"). + +## Stage 4 — Account for every edge + +Bound: `bin/cut-ledger.sh` exits 0 (0 unresolved rows). + +1. Write `slice.txt` (modules, plus `Module.Doc` lines for documents that move alone) and a + `cut-rules.tsv` in the format `bin/cut-ledger.sh`'s header gives; every rule carries evidence. +2. `bin/cut-ledger.sh --catalog --slice slice.txt --rules cut-rules.tsv + --check-log --exporter-json -o cut-ledger.tsv`. Exit 1 while any row + is unresolved. +3. Resolutions: `resolved[:how]` (moved, copied, starter, marketplace, odata, rest, page-odata, + local-owner), `stub` (green but + carries no behaviour — evidence says what is missing), `handoff` (named human work), `retire`, + `platform`. **The stub count is the progress measure**; a stub is never a resolution. +4. **Level 3:** ledger the monolith's in-edges too. **In-edge hand-offs become the measure** (field-service + template, increment 7: 499 → 157). Extra kinds: `resolved:rebind` (XPath text re-pointed), `resolved:pull` (device copy + pulled from the slice), `resolved:rewired`, `resolved:association` (re-created local → external). + A hand-off naming a deleted module is a defect, not a resolution. + +## Stage 5 — Build the seam + +Bound: every behaviour-carrying stub is replaced by a working call, or is still counted. + +Patterns, anti-patterns, external-entity limits and completion criteria for the seam itself live +in **`mendix-service-seam.md`** — read it before the first publish. Decompose-specific points: + +- **Reads: OData** as external entities, keyed per P1 (on the field-service template 9 of 10 core + entities had no safe existing key), each stub replaced in recipe A order. +- **Writes: a published REST action** when the write carries behaviour (P3), else Send External + Object. DateTime per P9. +- **Ownership flips.** Once the slice serves an entity it is the system of record; a local save + kept next to the REST call is a dual write, a hand-off row until one side goes (P13). +- **Errors.** Every REST call gets `on error without rollback`; OData-backed pages have no error + path (P4). +- **Security Production on both sides**, machine role only on the services (P2), credentials in + machine-local constants. API account passwords meet the Mendix password policy — a failing + after-startup create kills the boot — and a `__SET_ME__` sentinel needs a guard where code acts on + it (P11); production auth per P2. Stub entities need module roles too (CE2729 otherwise). +- Monolith edits: see Stage 5b step 7. + +## Stage 5b — Strangle (level 3 only) + +Bound: the monolith passes `mx check` 0 and `BUILD SUCCEEDED` with the slice modules deleted, and 0 +hand-off rows name a deleted module. + +Not describe → edit → exec: on the field-service template 25 of 38 referencing pages held widgets mxcli cannot author +(34 DataGrid, 21 ReferenceSelector). Edit in place: + +1. `move entity` each slice entity the monolith still uses into **one** consumer module (seam P6). +2. Swap each for a same-name external entity (seam recipe A order); pages and flows rebind by name. +3. Re-create monolith-owned associations local → external (seam limits → Associations). +4. `drop module` each slice module. +5. Work the `mx check` phases in order: **CE1613** (access rules and selections naming dropped + members — `drop module` leaves them), then **CE0161 / CE0463** (XPath across the seam; widget + definitions), then **CE2729** (module roles on the new entities). +6. Rebind XPath and expression text: `move entity` leaves qualified names in strings unchanged (62 + strings in 24 units still read `AssetManagement.Asset…`). Grep the old module names; 0 hits. +7. Monolith flows: edit only what needs editing. Edited flows went through drop + create, which + resets the layout and broke an unchanged decision (`[CE0117] "Error(s) in expression." at + Decision 'User is SDE?'`). Keep a per-unit restore path (a copy before each exec). +8. Offline push chains and the monolith's own delete cascades break silently: seam limits + "Offline" and P13. + +## Stage 6 — Prove and report the level + +Bound: the slice report names exactly one level. The three levels were CONFIRMED with the user +2026-10-12; level 3 was amended 2026-10-13 from field evidence: "one journey per crossing edge" +became **one journey per crossing kind**, because per edge was undefined for 536 in-edge rows +(increment 7). + +| Level | Criteria | +|---|---| +| 1 build-green | `cut-ledger.sh` 0 unresolved, `mx check` 0, `BUILD SUCCEEDED`, moved documents describe-identical | +| 2 seam-live | every behaviour-carrying stub replaced by a working REST/OData call, or handed off with a written reason when external entities cannot carry it (XPath, aggregates) — hand-offs counted separately from stubs; security Production, roles mapped | +| 3 strangled | monolith in-edges rewired or handed off (Stage 4 step 4); modules deleted from the monolith (Stage 5b); one journey per crossing kind (read, write, join, sync pull, offline forward) passes with data checked in both databases, and passes again on a second consecutive run (idempotent) | + +Calling level 1 "done" hides the stubs. Journeys follow `project-tests/e2e/journey-runner.js`: +landing guard and ledger pointer per step, data effects checked by `mxcli oql`, exit 2 for an +instrument fault. Measured: increment 7's journey across both apps passed 16/16 steps on two +consecutive runs. + +## Stage 7 — Learnings + +Bound: every finding of the run is in exactly one place. + +- mxcli defect with verbatim repro → a `contrib/inbox/` drop, triaged into `bug-logs/mxcli-bugs.md`. +- MDL rule an author would trip on → a `contrib/inbox/` drop, promoted to `skills/learned-mdl-preflight.md`. +- Judgement about cutting, sequencing, seams → this skill. Instrument gap → `bin/` with a fixture. + +`CHANGELOG.md` line in the same commit, crediting the source project. + +--- + +## Traps on this route (mxcli 0.26 and platform) + +Each line: trap → workaround → the increment that measured it. Tag **[fixed after 0.26.0]**: fixed on mxcli main after 0.26.0 (`nightly-80-gbd45c47e`, nightly retest 2026-10-13), not yet released — keep the workaround while `mxcli --version` reports ≤ 0.26.0; drop it once your mxcli includes the fix. + +- Microflow create/alter refused on 9.x **[fixed after 0.26.0]**; `mxcli run` fails on 9.x (nightly: launcher wants Java 11) → convert to 10.24 (increment 4). +- exec is not atomic; a recommended drop + create can stop after the drop → copy first, `mx check` after every exec (increments 3, 4). +- No `-p` silently picks any `.mpr` in the working directory → always pass `-p` (increment 3). +- `with all` omits nanoflows → describe them one by one (increment 3). +- JS/Java action type parameters describe as entity `.Entity` → hand-off row (increment 3). +- No native navigation profile can be created → native starter as target (increment 3). +- OData publish passes `check` with no key or a non-unique key → `PublicID: AutoNumber default 1`; expose bare attribute names, not quoted (quoting **[fixed after 0.26.0]**) (increment 5). +- `mxcli new` 11.x apps are MPR v2 → back up `.mpr` **and** `mprcontents/` (increment 6). +- 11.x consumed OData: `ConfigurationMicroflow:` holding a headers microflow → CE6808, and the slot cannot be cleared; `set (ConfigurationMicroflow: none)` stores a microflow named `none` that crashes `mx check` → drop and recreate the consumed service with `HeadersMicroflow:` only (increment 6). +- Combo box on an association passes `check`, fails exec or `mx check` → wrap in a data view (increment 5). +- `marketplace update` needs `--force` on a converted copy and leaves CE0463 → `mx update-widgets` with an absolute path; DataGrid 2 on 10.24 reads `CE0463 "The definition of this widget has changed"` until `mxcli fix widgets` (increments 4, 5, 7). +- Trial licence: machine OData sessions count against the user cap → `The OData endpoint returned 402 Payment Required` (increment 7), and the trial caps logins too (`Maximum number of sessions exceeded!`, increment 6) → restart the runtime between journey batches; for repeated e2e runs use a licensed test environment. +- `limit 1` retrieve is a list but `check` accepts object operations → use `first`, then drop + create (increment 4). Nightly: `mx check` names it CE7247 and a splice to `first` clears it; `mxcli check` still passes it. diff --git a/skills/learned-constants-and-secrets.md b/skills/learned-constants-and-secrets.md index f320475d..773f485c 100644 --- a/skills/learned-constants-and-secrets.md +++ b/skills/learned-constants-and-secrets.md @@ -165,6 +165,13 @@ GenAI problem: an empty string is a legal value that downstream code accepts and several layers away, in someone else's error message. A sentinel fails at the first use, with your constant's name in the message. +**A sentinel needs a guard wherever code acts on it** — e.g. create the API account at startup +only when the password constant is not `__SET_ME__`. **One integration secret, two stores:** an +app-to-app service password lives in both apps' machine-local stores; rotate both together. +**API account passwords must satisfy the Mendix password policy** — a policy failure inside the +after-startup account create aborts the boot (`M2EE: Starting Mendix Runtime failed.`; +`mendix-service-seam.md` P11). + For `Encryption.EncryptionKey` specifically: **exactly 32 characters**. 31 or 33 fails at runtime inside the same misleading error family. diff --git a/skills/mendix-service-seam.md b/skills/mendix-service-seam.md new file mode 100644 index 00000000..225f9132 --- /dev/null +++ b/skills/mendix-service-seam.md @@ -0,0 +1,185 @@ +# Mendix Service Seam — OData and REST Between Two Mendix Apps + +**Applies to:** any mxcli project where one Mendix app publishes a service another Mendix app +consumes — usually after a split (`decompose-app.md` Stage 5), also any app-to-app seam. +**Purpose:** Patterns that worked, and the concrete failure each one prevents, for publishing +OData/REST from one Mendix app and consuming it as external entities and REST calls in another. +**Evidence:** a public field-service template split on 2026-10-12: its monolith (Mendix 10.24) +publishing its core over OData and REST to a slice app on 11.13 (*core run*), then the slice's +modules deleted from the monolith, which reads the slice back (*strangled run*). Error texts below +are verbatim from those runs; their captures are not committed to the toolkit. Results: +`pipelines/mendix-mpr/README.md` → "Field runs". + +**Not this skill:** a third-party REST API with JSON structures and mappings → +`rest-integration-first-time-right.md`. Where to cut an app → `decompose-app.md`. + +--- + +## Patterns + +Each: do this → the failure it prevents → where it was captured. + +**P1 Key: add `PublicID: AutoNumber default 1`.** +`alter entity M.E add attribute if not exists PublicID: AutoNumber default 1;` — accepted as the +OData key without a unique rule (core run). `default 1` is +mandatory (MDL023/CE7247). **Grant read on `PublicID` to every module role with an access rule on +the entity** — page-created objects fail on commit otherwise: `Failed to commit: Value of +auto-number attribute 'PublicID' not available, could be caused by insufficient access rights …` +(strangled run). A unique rule on an existing attribute only when every create path fills it. +*Anti:* a key with no unique rule passes `check`, then `CE6624`; no key → +`CE6585 "Published entity '…' must have a key defined."` (first web slice run). + +**P2 Machine role only, on both services.** One machine user role (here `CoreAPIClient`) with one +read-only module role per published module, granted exactly the published members; create/delete +only where a REST action writes. Measure all three: +``` +GET odata/core/v1/Technician (machine role) HTTP 200 +GET odata/core/v1/Technician (no credentials) HTTP 401 +GET odata/core/v1/Technician (human role) HTTP 404 +``` +Basic auth with the password in each app's local constant store is the minimal route the field runs +proved; over TLS only. For production prefer token-based service auth via the OIDC SSO module (client +credentials); the Mendix SSO module is deprecated as of 1 May 2026. The publisher picks the method and +the consumer must match it (https://docs.mendix.com/refguide/consumed-odata-services/). +Measured on the core run. 401 = no or bad credentials; 404 = authenticated but the role has no access +to the service. *Anti:* a human role on the service exposes the API to every user login. + +**P3 REST action: primitive query params, String result, no mappings.** Behaviour-carrying writes +go to a published REST action backed by an upsert microflow: +`call rest service post '{1}/rest/core/v1/rosters?AssetSystemID={2}&…' … returns String` +(core run). Between Mendix apps no JSON structure or import mapping is +needed. Base URL in a per-environment constant. + +**P4 Every REST call has an error handler.** `on error without rollback` + log + user message. +*Anti 1:* the first seam call had none, so a far-side outage rolled back the caller's own commit +(first seam probe, 2026-10-12). +*Anti 2:* **OData-backed widgets have no error path.** With the owner stopped, opening an asset +raised `Exception while retrieving data for 'AssetManagement.Snippet_Asset_DataTabs…'` dialogs and +the page was unusable (core run). Mitigate with lazy tabs, or REST-fed local copies for +pages that must survive an outage — otherwise record an availability-coupling hand-off. + +**P5 Cross-app join via a published mirror** — see recipe B. +*Anti:* XPath over local ↔ external: `[CE0161] "Error(s) in XPath constraint."` +(core run); database-grid columns over the same path pass `mx check` silently. + +**P6 One consumer module per service where navigations are needed.** `create external entities` +creates navigation associations only inside one target module (`Created 8 navigation +associations`, core run). *Anti:* the same remote set in a second module passes mxcli, +then `[CE6609] "External entity with the same remote source already exists."` (core run). + +**P7 Delete through the owner's own delete flow** (a REST action calling e.g. `Roster_Delete`), so +its cascades and side effects still run. *Anti:* a raw delete skips them. Never delete-and-recreate +under the same key; soft-delete instead. + +**P8 Enumerations arrive as `Edm.String`.** Consumers compare strings, not enum values. + +**P9 DateTime as UTC ISO** (`2026-10-11T22:00:00.000Z`), or an agreed date-only convention. +*Anti:* a zone-less `yyyy-MM-dd` string was shifted by the runtime timezone and landed a day early +(core run). + +**P10 Version the path.** A new or changed published member is a contract change: publish +`…/v2/`, keep `v1` until every consumer moved. + +**P11 Secrets.** Basic-auth password only in each app's machine-local constant store (one secret, +two stores); model default `__SET_ME__` plus a guard wherever code acts on it; the API account +password meets the Mendix password policy (`learned-constants-and-secrets.md`). +*Anti:* a generated password with no symbol failed inside the after-startup account create — +`This requires the password to contain non-alphanumeric characters` → +`M2EE: Starting Mendix Runtime failed.` (core run). + +**P12 Consumer auth on 11.x: `HeadersMicroflow:`, not `ConfigurationMicroflow:`.** +*Anti:* `[CE6808] "The microflow that configures a consumed OData service should return a +System.ConsumedODataConfiguration"` (core run, on 11.13; the same model passed on 10.24). The slot cannot be +cleared afterwards, and `set (ConfigurationMicroflow: none)` stores a microflow named `none` that +crashes `mx check`. Fix: drop the consumed service and recreate it with `HeadersMicroflow:` only — +external entities survive. + +**P13 Ownership.** A slice-owned association to an external entity loses the owner's delete +cascade: when the far side deletes, local rows orphan. The same holds from the monolith side: its +own delete cascades into slice rows are lost once those rows move (field-service template: +`AppointmentList_Delete`, `WorkOrderList_Delete`). Fix: a published REST delete-for-parent action on the owner (P7), or an +orphan hand-off row. A local save next to a REST write is a dual write — a hand-off until one side +goes. + +--- + +## External-entity limits + +What re-pointing a consumer at an external entity costs — `mx check` will not tell you all of it: + +- **Associations:** local → external only with the **local entity as owner**; an external entity + cannot own one (hand-off). A reference set local → external works, but entity-path widgets over it + fail `[CE6634] "An association to or from an external entity is not allowed in an entity path + source."` (strangled run). A slice-owned association to a monolith entity cannot be + published — publish a key copy (B′). Inherited attributes cannot be published via mxcli: give each + specialization its own key. +- **XPath:** cannot traverse local ↔ external — retrieves, access rules, pages and nanoflows using + one break. Also CE0161: comparing the local entity's own local → external association with an + object (`[TicketManagement.Ticket_Asset = $Asset]`), and retrieving an external entity by reverse + association. Retrieve over the navigation instead. Access-rule disjuncts that walk such a path + become `[false()]` or need a local owner entity (recipe B, P5). +- **Writes:** Commit/Change/Delete on an external entity do not work — rewrite to Send External + Object (Insertable/Updatable/Deletable publish) or a REST action (P3). The consumer cannot create + an external object: post a non-persistent draft. +- **Aggregates:** count works; sum/avg/min/max run in memory or not at all; no OQL or datasets. + Totals need a published aggregate or a local copy. +- **Offline:** native offline profiles cannot hold external entities — REST-fed local copies. + Push chains that ended in the owner's commit flows die when those flows move: rebind each to the + REST write and add a pull entry (field-service template: `NativeSync_Pull*`). +- **Security:** external entities need read grants to a consumer module role. Granting a module + role of one module on another module's entity → `[CE0007] "Please re-select module roles."` + (core run). + +Source: https://docs.mendix.com/refguide/external-entities/ and `published-odata-entity/`. + +--- + +## Recipe A — Replace a stub by an external entity + +Pages and flows rebind by qualified name, so the external entity takes the stub's name. Order +matters because `drop entity` silently deletes every cross-module association pointing at it +(and their member rights), and navigations exist only within one module (P6): + +1. Record every association the stub had (`describe entity`), and which side owned it. +2. Drop the stub's owned associations. +3. Drop the stub entities. +4. `create external entities` for the whole service into **one** consumer module. +5. Re-create the slice-owned associations (local entity as owner). +6. Rewire consumers by name; `mx check` after every exec. + + +## Recipe B — Cross-app join via a published mirror + +The consumer needs "tickets of this asset" but the asset lives in the consumer and the tickets in +the owner. + +1. The owner keeps (or upserts) a mirror of the consumer entity keyed by the **consumer's** key — + `MonoAsset` keyed `SystemID` with a unique rule — and publishes it. +2. The consumer filters on the key, not on an association across apps: + `[Ticket_Asset/MonoAsset/SystemID = $Asset/SystemID]` (core run, "filtered over the navigation"). +3. Consumer writes reach the owner via REST, which upserts the mirror (dual owner — hand-off row). +4. Where an access rule must walk the join, a local owner entity carries it + (`CoreSeam.TechnicianLink`, names copied from OData at link time). + +The mirror is **temporary**: at level 3 it lives in the module being deleted. **B′:** the +owner-side entity carries a copy of the consumer's key, set before commit and published; the +consumer filters on it. Removing the mirror from a published service is a breaking change (P10). + +--- + +## Completion criteria + +Bound for a seam: every line below holds, each with its count stated. + +- Every published service grants **exactly one** machine role, measured 200 / 401 / 404 per + service (N of N services captured). +- Every published key is `PublicID` AutoNumber or an attribute with a unique rule filled by every + create path, readable by every module role with an access rule on its entity (N of N keys named + with their kind). +- Every `call rest service` has `on error` (0 calls without; grep the MDL). +- Every external entity on a page is listed with its outage behaviour — lazy tab, local copy, or + availability-coupling hand-off (N of N pages; "not probed" is legal, silence is not). +- Every external entity has a read grant; `mx check` 0 on both apps. +- Every slice-owned association to an external entity has an orphan hand-off row or a reason + it cannot orphan. +- Every DateTime crossing the seam is UTC ISO or under a written date-only convention. diff --git a/skills/rehost-app.md b/skills/rehost-app.md new file mode 100644 index 00000000..d53276c9 --- /dev/null +++ b/skills/rehost-app.md @@ -0,0 +1,172 @@ +# Rehost App — Regenerating an Existing Mendix App as MDL into a Fresh App + +**Applies to:** migration — a Mendix model (`.mpr`, Mendix 9 or later) is the legacy source, and the +whole app is regenerated as MDL into a clean target app. +**Purpose:** Run the Rehost extractor, drive its check → quarantine → exec → resume loop to a +fidelity ledger, and report the done-criterion — *build-green with hand-offs plus journey parity on +the main flows* — instead of a fidelity percentage. Upstream of the ledger this is a pipeline (`pipelines/mendix-mpr/`); +downstream, runbook Stages 2–6 apply unchanged (Stages 2–4 are WAIVED with a reason for a 1:1 +regenerate). +**Evidence:** `pipelines/mendix-mpr/README.md` → "Field runs" — public apps only: a banking sample +(Mendix 11.13, 155 documents, build-green), a GenAI showcase app (Mendix 11.12, 2776 documents, 36/36 +modules landed, not build-green) and a field-service template (Mendix 9.24, read probe and MPK moves). + +--- + +## Step 0 — Rehost or MPK move? + +Bound: the choice and its reason are a `PROJECT.md` register line. + +| You want | Route | +|---|---| +| The whole app re-platformed into a clean project; MDL source you own afterwards | **Rehost** (this skill) | +| One or a few modules moved into another app, Java bodies and IDs intact | **MPK move** — `decompose-app.md` Stage 3 | +| Split a monolith into several apps | `decompose-app.md` (it moves slices by MPK, not by regeneration) | + +**Rehost vs MPK move.** Rehost (MDL regeneration) is a full re-platform into a clean project, and it +fails wherever mxcli's describe cannot express the model. An MPK move carries modules as they are: +byte-identical, IDs preserved, no clean-up. The MPK route is `decompose-app.md`. + +Measured scope: Rehost has run same-version only (11.13 → 11.13, 11.12 → 11.12). A 9/10 source read +works (field-service template 9.24 read probe), but regeneration from 9/10 into 11 is **not yet exercised** +(`pipelines/mendix-mpr/README.md` → "Not yet exercised"). Say so in the plan; do not promise it. + +## Step 1 — Prepare the target + +Bound: the target opens in `mx check`; every row below has a state, and every hand-off row an owner +in `PROJECT.md`. + +Tag **[fixed after 0.26.0]**: fixed on mxcli main after 0.26.0 (`nightly-80-gbd45c47e`, nightly retest 2026-10-13), not yet released — keep the workaround while `mxcli --version` reports ≤ 0.26.0; drop it once your mxcli includes the fix. + +`mxcli new --version --theme none`, then walk what a rehost carries: + +| Item | State | Action | +|---|---|---| +| Entities, flows, pages, nanoflows | copied; gaps on large apps | Steps 2–4; ledger rows | +| Java actions | user code copied; helper classes and jars **not** | copy helpers and userlib jars by hand. The MDL route lands only the action body; without its helpers mxbuild failed `package org.apache.tika does not exist` (field-service template, 2026-10-12) | +| User roles, app settings | copied (`describe userrole`, `describe settings`) | `describe userrole` drops `CheckSecurity: false` — re-set it **[fixed after 0.26.0]** | +| Demo-user passwords, project security level | `***` / a report — hand-off | re-enter real passwords; write `alter app security` | +| Constants | copied with default values | per-environment values: environment row | +| Navigation | not emitted by the extractor | re-enter menus and home pages; record as hand-off | +| Theme / styling | not carried (`(app)` ledger row) | copy `theme/` and `themesource//` | +| Marketplace modules | re-installed, never regenerated (`mxcli marketplace install`) | each gap is a hand-off: Mendix-9-built packages refuse into 11 (3 on the GenAI showcase app; convert through 10.24 or replace); delisted versions; template modules at other versions | +| Custom widgets | installed from the source's `.mpk` files | before Step 2 | +| Environment configuration: certificates, DB credentials, per-environment constants, scheduled events on/off, deployment settings | outside the `.mpr` entirely | its own per-environment checklist, owned by whoever runs the environment | + +**CRLF inside string literals is load-bearing — never normalise a `describe` file.** Studio Pro stores annotation text with `\r\n` and `describe` emits it verbatim inside the `'…'` literal. A helper that reads the file in text mode (Python universal newlines, `dos2unix`, an editor on save) silently writes `\n`; `check` then refuses the flow with `this change cannot be spliced into the stored flow: the free annotations change … [MDL-V1-REBUILD]` (mxcli v0.26.0, 2026-10-12). Read and write byte-for-byte (`newline=''`, `sed -b`) and treat a `.mdl` as binary in any step that touches it. + +## Step 2 — Run the extractor + +Bound: `fidelity-ledger.tsv` exists and its header states the denominator (every describable +document in the source catalog for the modules run). + +``` +MXCLI= pipelines/mendix-mpr/rehost-extract.sh [Module ...] +``` + +Read the script header for outputs. Defaults: every own module (empty Source column), System +excluded. Switches: +- `REHOST_REUSE_MDL=1` — reuse `/mdl/.mdl` from an earlier run; use it on every rerun after + fixing the check/exec side, because the describes are the slow part. +- `REHOST_KEEP_ORDER=1` — replay modules in the order given instead of reference order (use when + you have resolved a module cycle by hand). + +**Budget** (GenAI showcase app, 36 modules): describes ~30 min; first check/exec ~14 min; rerun with reused +describes 36 min. One module: 35 s. + +## Step 3 — Understand the loop the script runs + +Bound: for every module, either it landed or the run log says why it did not. + +Per module, in order: +1. `describe module with all` via `-c`, plus every document `with all` omits (nanoflows, + snippets, JS actions, JSON structures, mappings, task queues, image collections), described one + by one from the project tree. +2. `lib/topo-order-mdl.py` — callees before callers; a flow and its grants move as one unit. +3. `lib/fix-describe-output.py` — repairs the Java-action parameter comma mxcli emits inside a + `--` comment **[fixed after 0.26.0]**; on a fixed mxcli it moves 0 commas and can be skipped (keep it). +4. **check → quarantine**: `mxcli check` is all-or-nothing per file; `lib/quarantine-mdl.py` moves + the statements it rejects (with their same-module closure) to `.deferred.mdl`. +5. **exec → resume**: exec stops at the first statement it cannot write; `lib/exec-resume.py` + defers that statement with the verbatim error and continues with the rest. +6. Deferred statements retry in fixed-point passes (most are forward references into modules not + yet created); what still fails reaches the ledger with its error. + +The failure this loop exists for: the first full run on the GenAI showcase app (2026-10-12) +executed **1 of 36 modules** — one emitter defect plus all-or-nothing check sank the other 35. + +When quarantine and exec-resume cannot place an error on a statement, the script falls back to +`lib/exec-each.py` (one statement at a time). Before that fallback, the rest of the module dropped +silently: 7 modules, 1520 ledger rows reading `absent from target describe` (GenAI showcase app, +2026-10-12; fixed 2026-10-13, 7/7 modules then landed). A whole module of `failed-exec` rows is an +extractor fault, not N failed documents. Count what never ran from the `-- deferred:` lines in +`.deferred.mdl`, not from the run log. + +## Step 4 — Read the fidelity ledger + +Bound: every row is landed or a named hand-off; the hand-off count is reported. + +| Verdict | Means | Action | +|---|---|---| +| `emitted-identical` | target describe equals source | none — the build still decides | +| `emitted-drift` | landed, describe differs | read the evidence; on the GenAI showcase app ~190 of 271 were microflow layout (#923) or doc whitespace | +| `failed-exec` | not in the target | find the first error naming it: a dropped module, a deferred dependency, or a root cause | +| `not-emitted` | in the catalog, no describe statement | hand-off or extractor gap | +| `not-expressible` | MDL cannot carry it (NOT re-executable widgets, #923 decisions, app-level rows) | hand-off | +| `target-extra` | only in the target | check it is the template's, not a stray | + +**The ledger does not decide done; the build gate does.** The failure: a sort on an inherited +attribute was `emitted-identical` on the ledger and still CE1613 in `mx check` (GenAI showcase app, +2026-10-12; that defect is **[fixed after 0.26.0]**, the lesson stands). + +## Step 5 — Gate and report + +Bound: the report states `mx check` errors, mxbuild result, and the hand-off count by class. + +**Build gate** (CONFIRMED 2026-10-12): `mx check` 0, `BUILD SUCCEEDED`, every ledger row landed or +a named hand-off. Run it yourself — the script does not (`project-bin/exec.sh`'s mxbuild gate, or +`mx check `). Then Step 5b; done needs both. + +Hand-off classes by construction in mxcli 0.26 — record each document, fix in Studio Pro: + +| Class | Why | +|---|---| +| Java action type parameters | described with no type parameter; exec writes entity `.Entity` (CE1613) | +| ~~Message definitions~~ | **not a hand-off** (corrected 2026-10-12): `create message definition collection` exists in 0.26.0; the extractor must emit the collection before the export mappings that use it. The GenAI showcase failure was ordering, and the error text does not point at the command | +| Rules | cannot be described or created **[fixed after 0.26.0]**; once fixed, check each decision call — `describe microflow` drops the rule's argument. Also missing from the extractor's denominator | +| Mutual recursion | exec refuses forward references, so both flows and their callers stay deferred | +| NOT re-executable widgets | describe marks them; e.g. `Forms$FileManager` | +| Step 1 hand-off rows | not carried by describe | + +If the model will not load (`ArgumentNullException … EntityType.set_EntityId`), mxcli wrote a +microflow whose `import from mapping` result has a null entity: `drop microflow` then the source +`create or modify` in one script (GenAI showcase app, 2026-10-12). **[fixed after 0.26.0]**: exec refuses the forward reference +(MDL-ORDER01) and writes nothing — move the mapping above the microflow. + +## Step 5b — Journey parity, source vs target + +Bound: N/N journeys pass on **both** apps with the same data effects, or the report says +`journeys NOT RUN: `. Silence is not a result. + +Describe-identical is not behaviour-identical (the CE1613 sort above). Run the same Playwright journeys against the source app and the +rehosted app and compare outcomes, with the existing machinery — `journey-proof.md` for the journey +shape, `e2e-harness-base.md` for the harness (as `existing-app-assurance.md` uses it), `project-tests/e2e/journey-runner.js` to run them. + +1. One journey per main user flow; assert its data effect in both databases. +2. Make every journey idempotent: run-unique IDs, never an empty grid assumed. Failure: the + field-service template journey (2026-10-12) broke on its third run because accumulated rows let + the grid's sticky header cover "New Part". Run each twice. +3. A journey passing on one app only is a rehost defect; trace it to its ledger row. + +Measured so far: the banking sample (same-version, 155 docs) was proven statically only; the GenAI +showcase app ran no journey (it needs live LLM credentials). + +**Done = build-green with hand-offs plus journey parity on the main flows** — or build-green with +hand-offs and `journeys NOT RUN` with a written reason. Say which. + +## Step 6 — Learnings + +New mxcli defects → a `contrib/inbox/` drop (the contribution lane); extractor defects → the +pipeline with a captured fixture; judgement about what to regenerate or hand off → this skill. Known +emitter defects are in `pipelines/mendix-mpr/README.md` ("Field runs", "mxcli findings") and the +pipeline's `lib/*.py` headers — read them before re-diagnosing. `CHANGELOG.md` line in the same commit, crediting the source app.