Skip to content

Commit 957e764

Browse files
akoclaude
andcommitted
fix(mcp): ListDomainModels must reconstruct dirty existing modules
CREATE ASSOCIATION failed over --mcp when its from/to entities were created earlier in the same script, inside an existing module: Created entity: MyFirstModule.Address Error: parent entity not found: MyFirstModule.Address The executor resolves an association's entities via findEntity → ListDomainModels. GetDomainModel already reconstructs a written-this-session module from Studio Pro's live model (dirty routing), but ListDomainModels only reconstructed *session- created* modules — for a *dirty existing* module it returned the stale on-disk snapshot, so entities created earlier in the run were invisible. The two read paths were inconsistent; this is why new-module scripts worked but an existing module (e.g. MyFirstModule in a fresh app) failed. Apply the same dirty routing in ListDomainModels: reconstruct any local domain model whose module is dirty, then append session-created modules as before. Repro (MCP-only, existing module): mdl-examples/bug-tests/mcp-association-existing-module.mdl. Live verification pending Studio Pro restart; the fix mirrors GetDomainModel's proven routing and is build/test-clean. Not unit-tested in isolation because the backend's *mpr.Reader has no mock seam (consistent with the package's tests). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent bd82d1e commit 957e764

2 files changed

Lines changed: 66 additions & 6 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
-- ============================================================================
2+
-- Bug — CREATE ASSOCIATION fails over --mcp when the from/to entities were
3+
-- created earlier in the SAME run, inside an EXISTING module.
4+
--
5+
-- Created entity: MyFirstModule.Contact
6+
-- Created entity: MyFirstModule.Address
7+
-- Error: parent entity not found: MyFirstModule.Address
8+
--
9+
-- Root cause: the executor resolves an association's from/to entities via
10+
-- findEntity, which lists modules through Backend.ListDomainModels. In the MCP
11+
-- backend that method reconstructed only *session-created* modules from Studio
12+
-- Pro's live model — for a *dirty existing* module it returned the stale on-disk
13+
-- snapshot, hiding entities created earlier in the run. (Backend.GetDomainModel
14+
-- already had the correct dirty routing; ListDomainModels did not — the two were
15+
-- inconsistent.) Fix: ListDomainModels applies the same dirty routing.
16+
--
17+
-- IMPORTANT — how to reproduce: run this OVER --mcp against a project where
18+
-- MyFirstModule ALREADY EXISTS on disk (e.g. a fresh Mendix app). The bug is
19+
-- specific to dirty *existing* modules: if the module is created in-script it
20+
-- becomes a "session module", which always reconstructed correctly and does NOT
21+
-- reproduce. On the file (MPR) backend this also passes — the bug is MCP-only.
22+
--
23+
-- mxcli exec mcp-association-existing-module.mdl -p app.mpr \
24+
-- --mcp http://localhost:7782/mcp
25+
--
26+
-- Expected after the fix: both entities and the association are created; no
27+
-- "parent entity not found".
28+
-- ============================================================================
29+
30+
create persistent entity MyFirstModule.BugContact (
31+
Name: string(100) not null error 'Name is required'
32+
);
33+
/
34+
35+
create persistent entity MyFirstModule.BugAddress (
36+
Street: string(200),
37+
City: string(100)
38+
);
39+
/
40+
41+
-- This statement failed before the fix because BugAddress (just created over MCP)
42+
-- was not visible to the association's entity resolution.
43+
create association MyFirstModule.BugContact_BugAddress
44+
from MyFirstModule.BugContact to MyFirstModule.BugAddress
45+
type reference
46+
owner default
47+
delete_behavior DELETE_BUT_KEEP_REFERENCES;
48+
/

mdl/backend/mcp/backend.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -321,13 +321,25 @@ func (b *Backend) ListDomainModels() ([]*domainmodel.DomainModel, error) {
321321
if err != nil {
322322
return nil, err
323323
}
324-
if len(b.sessionModules) == 0 {
325-
return local, nil
324+
out := make([]*domainmodel.DomainModel, 0, len(local)+len(b.sessionModules))
325+
// A module written this session must be reconstructed from Studio Pro's live
326+
// model here too, with the SAME dirty routing GetDomainModel applies — otherwise
327+
// the stale on-disk snapshot hides entities/associations created earlier in the
328+
// same run. This is what made CREATE ASSOCIATION fail when its from/to entities
329+
// were just created in an existing module: findEntity lists via this method, and
330+
// it previously reconstructed only session-created modules, not dirty existing
331+
// ones. Best-effort: keep the local copy if reconstruction fails.
332+
for _, dm := range local {
333+
if mod, merr := b.reader.GetModule(dm.ContainerID); merr == nil && b.dirty[mod.Name] {
334+
if recon, rerr := b.reconstructDomainModel(mod.Name, dm.ContainerID); rerr == nil {
335+
out = append(out, recon)
336+
continue
337+
}
338+
}
339+
out = append(out, dm)
326340
}
327-
// Append a live-reconstructed domain model for each session-created module, so
328-
// entity references into a freshly created module (e.g. a microflow parameter
329-
// or workflow context typed X.Foo) resolve in the same run. Best-effort.
330-
out := append([]*domainmodel.DomainModel(nil), local...)
341+
// Session-created modules have no on-disk domain model; reconstruct each so
342+
// references into a freshly created module resolve in the same run.
331343
for _, m := range b.sessionModules {
332344
if dm, derr := b.reconstructDomainModelFromPED(m.Name, model.ID(sessionDMPrefix+m.Name), m.ID); derr == nil {
333345
out = append(out, dm)

0 commit comments

Comments
 (0)