You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 1b343fa
Browse filesBrowse the repository at this point in the historyBrowse files
fix: moving an OWNER Both association's TO end leaves stale member access, and CE0001 associations are written (ako#819)
Three follow-ups from #802 (PR #809); two were still open on main.
1. `create or modify association` moving a cross-module OWNER Both
association's TO end to another module (or entity) printed "Modified
association" and mx check reported CE0066 "Entity access is out of date"
at the old TO module and the new one. Changing only the owner through
create or modify missed the TO module the same way.
Cause: the cross-module OR MODIFY branch of execCreateAssociation
(mdl/executor/cmd_associations.go) overwrote ChildRef/owner and reconciled
nothing; the end-of-program reconcile covers the declaring module only.
And ReconcileMemberAccesses kept the old TO entity's entry even when its
module was reconciled, because a reference to another module's existing
association was preserved on faith (#758's rule for inheritance across
modules).
Fix: the branch reads the old TO module before overwriting and reconciles
declaring, old TO and new TO modules (reconcileModulesAccess). The
reconcile treats an existing foreign association as stale on an entity
whose generalization chain never leaves its module — its members are then
fully known (own + incoming OWNER Both). Inheritance across modules stays
preserved (control test).
2. Between a persistent and a non-persistent entity, Mendix accepts only
FROM the non-persistent end with owner Default; `owner both`, a persistent
FROM end, and `alter association … set owner both` all passed check and
exec and failed CE0001. Now refused as MDL-ASSOC01 in `check`
(ValidateProgram, script declarations first, then the project) and in
`exec` (create, create or modify, alter set owner both — also under
--no-check), through one predicate (associationPersistenceViolation) and
one stored-ends lookup (storedAssociationEnds).
3. GRANT accepting the TO-side member of a cross-module OWNER Both
association already works on main (74bc820): `grant read (Number,
Lead_Order) on entity Shop.Order …` writes the member, mx check 0 errors.
No change.
Verified on a blank 11.13.0 app with security on, mx check before (baseline
binary) / after: TO end moved to another module 2 -> 0 (CE0066 at both TO
modules), within the module 1 -> 0, owner Default->Both via create or modify
1 -> 0; `update security` repairs the move broken by the baseline binary.
CE0001 matrix: NP->P both, P->NP default, P->NP both, cross-module NP->P both,
ReferenceSet NP->P both and alter set owner both were each 1 CE0001 before and
are refused now with the project untouched (0 errors); NP->P default is still
written (0 errors). No skill or mdl-examples script trips the new check.
Tests revert-proven (fix reverted, predicate stubbed): every new test fails,
the controls pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
{"area": "mdl/executor", "date": "2026-10-25", "symptom": "ako/mxcli#819 (follow-ups of #802), measured on a blank 11.13.0 app with security on. (1) `create or modify association Crm.Lead_Order from Crm.Lead to Hr.Employee owner both` — moving a cross-module OWNER Both association's TO end from Shop.Order — printed \"Modified association\" and mx check reported CE0066 \"Entity access is out of date\" at the domain models of both Shop (old TO) and Hr (new TO); moving it to another entity of the same module, or only changing the owner through create or modify, left CE0066 at the TO module. `update security` repaired Hr but not Shop. (2) `create association … from <non-persistent> to <persistent> owner both` passed check and exec and failed CE0001 \"An association between a persistable entity and a non-persistable entity must start in the non-persistable entity and have owner 'Default'.\"; so did a persistent FROM end under owner Default, and `alter association … set owner both`. (3) GRANT naming the TO-side member of a cross-module OWNER Both association (`read (Number, Lead_Order)`) already worked on main — fixed by 74bc820b7, verified 0 errors.", "cause": "(1) The cross-module OR MODIFY branch of execCreateAssociation overwrote ChildRef and owner and reconciled nothing itself; the end-of-program reconcile covered only the declaring module. And ReconcileMemberAccesses kept the old TO entity's entry even when its module was reconciled: a reference to another module's association was preserved on faith (the #758 rule for associations inherited across modules), and this one still exists, just not as a member. (2) No check or exec guard modelled CE0001.", "file": "mdl/executor/cmd_associations.go (OR MODIFY cross branch reconciles declaring, old TO and new TO modules via reconcileModulesAccess; refusals on create and alter set owner both); mdl/executor/validate_association_persistence.go (associationPersistenceViolation, projectEntityPersistence, storedAssociationEnds, ValidateAssociationPersistence — wired into ValidateProgram); mdl/backend/modelsdk/domainmodel_security_write.go (staleForeignRef: an existing foreign association on an entity whose generalization chain never leaves its module is stale unless collected as an incoming OWNER Both)", "insight": "The issue named one half of each defect. The move left a stale entry on the OLD TO module and a missing one on the NEW TO module — read the old ChildRef before overwriting it — and the CE0001 rule is two-sided: mxbuild's message (and a 4-cell measurement: NP->P default ok, NP->P both, P->NP default, P->NP both all CE0001) says FROM must be the non-persistable end AND owner Default, so refusing only `owner both` would have left the persistent-FROM case writing a broken model. Measure the whole matrix before scoping a refusal to the reported cell. For the reconcile, 'cannot be checked' was only true when the entity inherits from another module: with a module-local generalization chain the full member set is known (own + incoming OWNER Both), so anything else foreign is stale; the cross-module-inheritance control stays green. Persistability for the refusal comes from the backend (specializations resolved from their root, mendixlabs/mxcli#1600) in exec and from the script's own declarations first in check, through one predicate. A single bug-test script under-reports the move on the old binary: grants track Hr, so the end-of-program reconcile repairs the new TO module and only the old one shows.", "refs": ["ako/mxcli#819", "ako/mxcli#802", "ako/mxcli#1449"], "ce": ["CE0066", "CE0001"], "rules": ["MDL-ASSOC01"], "test": "mdl/executor/association_persistence_819_test.go (TestAssociationPersistenceViolation_MatchesMxbuild, TestValidateProgram_RefusesOwnerBothAcrossPersistenceFromTheScript, TestExecAssociation_RefusesOwnerBothAcrossPersistence, TestValidateAssociationPersistence_ResolvesTheProject, TestCreateOrModifyAssociation_MovingTheToEndReconcilesBothToModules); mdl/backend/modelsdk/reconcile_foreign_stale_assoc_test.go (TestReconcile_DropsAForeignAssociationWhoseToEndMoved, control TestReconcile_KeepsAForeignAssociationInheritedAcrossModules); mdl-examples/bug-tests/access-819-association-owner-both-member.mdl"}
Copy file name to clipboardExpand all lines: cmd/mxcli/syntax/features_domain_model.go
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -298,7 +298,7 @@ func init() {
298
298
"many-to-one", "many-to-many", "foreign key",
299
299
"owner", "delete behavior",
300
300
},
301
-
Syntax: "[@anchor(from: (x, y), to: (x, y))]\nCREATE [OR MODIFY] ASSOCIATION Module.Name\n FROM Module.FromEntity TO Module.ToEntity\n [TYPE Reference|ReferenceSet]\n [OWNER Default|Both]\n [STORAGE COLUMN|TABLE]\n [ON DELETE CASCADE|RESTRICT|SET NULL [ERROR MESSAGE 'text']];\n-- Documentation: the /** … */ doc comment before the statement\n-- (COMMENT 'text' is its deprecated alias, MDL-DEPR100).\nALTER ASSOCIATION Module.Name SET ANCHOR FROM (x, y) TO (x, y);\nDROP ASSOCIATION Module.Name;\n\nOR MODIFY: updates type/owner/delete behavior in-place, preserves UUID.\n\n@anchor sets the LINE ANCHORS — where the connector attaches to each entity box\nin the domain model editor — as a PERCENTAGE of the box (0..100, whole numbers).\n`from` is the FROM entity's box, `to` the TO entity's: (0, 50) is the middle of\nthe left edge, (100, 50) the right, (50, 100) the bottom centre. Omitting an end\nPRESERVES what is stored, so a CREATE OR MODIFY about something else never\nflattens a hand-tuned line. Cross-module associations have no anchors.\n\nDROP reconciles the entity access rules that named the association, and is\nREFUSED while a message definition still exposes it (that one cannot be\nreconciled -- removing the member would change a published contract). The\nrefusal prints the `alter message definition ... drop member` statement for\neach definition, ready to run.",
301
+
Syntax: "[@anchor(from: (x, y), to: (x, y))]\nCREATE [OR MODIFY] ASSOCIATION Module.Name\n FROM Module.FromEntity TO Module.ToEntity\n [TYPE Reference|ReferenceSet]\n [OWNER Default|Both]\n [STORAGE COLUMN|TABLE]\n [ON DELETE CASCADE|RESTRICT|SET NULL [ERROR MESSAGE 'text']];\n-- Documentation: the /** … */ doc comment before the statement\n-- (COMMENT 'text' is its deprecated alias, MDL-DEPR100).\nALTER ASSOCIATION Module.Name SET ANCHOR FROM (x, y) TO (x, y);\nDROP ASSOCIATION Module.Name;\n\nOR MODIFY: updates type/owner/delete behavior in-place, preserves UUID.\n\n@anchor sets the LINE ANCHORS — where the connector attaches to each entity box\nin the domain model editor — as a PERCENTAGE of the box (0..100, whole numbers).\n`from` is the FROM entity's box, `to` the TO entity's: (0, 50) is the middle of\nthe left edge, (100, 50) the right, (50, 100) the bottom centre. Omitting an end\nPRESERVES what is stored, so a CREATE OR MODIFY about something else never\nflattens a hand-tuned line. Cross-module associations have no anchors.\n\nPersistent <-> non-persistent: FROM the non-persistent entity, OWNER Default.\nA persistent FROM end or OWNER Both there is CE0001 and is refused\n(MDL-ASSOC01), on CREATE and on ALTER ... SET OWNER Both.\n\nDROP reconciles the entity access rules that named the association, and is\nREFUSED while a message definition still exposes it (that one cannot be\nreconciled -- removing the member would change a published contract). The\nrefusal prints the `alter message definition ... drop member` statement for\neach definition, ready to run.",
302
302
Example: "mdl 1;\n-- Many-to-one\nCREATE ASSOCIATION Shop.Order_Customer\n FROM Shop.Order TO Shop.Customer\n TYPE Reference\n OWNER Default\n ON DELETE SET NULL;\n\n-- Many-to-many\nCREATE ASSOCIATION Shop.Product_Tag\n FROM Shop.Product TO Shop.Tag\n TYPE ReferenceSet\n OWNER Both;\n\n-- Line leaving the bottom of Order and entering the top of Customer\n@anchor(from: (50, 100), to: (50, 0))\nCREATE ASSOCIATION Shop.Order_Customer\n FROM Shop.Order TO Shop.Customer;\n\n-- Retune the line without restating the association\nALTER ASSOCIATION Shop.Order_Customer SET ANCHOR FROM (0, 54) TO (100, 54);",
Copy file name to clipboardExpand all lines: docs-site/src/reference/domain-model/create-association.md
+4Lines changed: 4 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -63,6 +63,10 @@ A documentation comment (`/** ... */`) placed before the statement is preserved
63
63
**OWNER**
64
64
: Which side can modify the association. Defaults to `Default` if omitted.
65
65
66
+
: Between a persistent and a non-persistent entity, Mendix allows one shape only: `FROM` the non-persistent entity, with owner `Default`. A persistent `FROM` end or `OWNER Both` there fails the build with CE0001, so `mxcli check` and `exec` refuse it (MDL-ASSOC01). `ALTER ASSOCIATION … SET OWNER Both` is refused the same way.
67
+
68
+
: Under `OWNER Both` the association is a member of the TO entity too, so its access rules get an entry for it — in the TO entity's module, for a cross-module association. `CREATE OR MODIFY` that moves the TO end, or changes the owner, reconciles the old and the new TO module's rules.
69
+
66
70
**ON DELETE**
67
71
: What happens to associated objects when a FROM-side object is deleted. If omitted, references are kept (the default Mendix behavior).
0 commit comments