Sync ako/mxcli: sort over associations, page reference validation, rewrite-churn fixes (31 commits) - #1156
Merged
Conversation
A page's widgets live in two AST fields. `Widgets` is the bare body; content addressed to a named layout placeholder is held apart in `Placeholders` (#532). validate.go passed `s.Widgets` alone to all three page validators, so every reference inside a `placeholder X { … }` block — microflow, nanoflow, page, snippet, entity, image — was validated by nothing. That is not an edge shape: `placeholder Main { … }` is what mxcli's own skills, its bug-test examples and its DESCRIBE output write. Measured on a blank Mendix 11.14.0 project, the same button in the two positions: placeholder Main { actionbutton btn (Action: MICROFLOW Mod.NoSuch) } -> ✓ All references valid actionbutton btn (Action: MICROFLOW Mod.NoSuch) (bare body) -> microflow not found: Mod.NoSuch It is the third copy of one walk — validateIconRefs (mendixlabs#1008) and forEachWidget each grew the placeholder arm separately, both with a comment saying a missed walk is silent in both directions — so the roots are collected once in allPageWidgets rather than added to a fourth walker later. Control: with allPageWidgets reduced to `return s.Widgets`, the placeholder test fails with the reported symptom (`got []`) and the bare-body control still passes, so the test detects the bug rather than merely agreeing with the fix. A reference that resolves stays silent, which is the direction a widened walk is most likely to get wrong. Refs: mendixlabs#1149 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ExDoCXFBfqq3R6j2qKx2Kf
mendixlabs#1149 reports that a Selection helper's custom states cannot hold a StaticImageViewer. The authoring half landed with mendixlabs#1057 — on main the reporter's page describes back with three `staticimage` lines and their `Image:` clauses, and re-running the description reports `Unchanged page`. What was still missing is the rest of the request, "a reference to an image from an image collection": the name could be written and nothing resolved it. widgetRefCollector keyed the image reference on the widget TYPE, so the pluggable `image` widget was collected and the two widgets mendixlabs#1057 had just given the same property were not — `staticimage`'s `Image` and `dynamicimage`'s `DefaultImage`. Measured on a blank Mendix 11.14.0 project: mxcli check sh.mdl -p app.mpr --references -> Check passed! mxcli exec sh.mdl -p app.mpr -> Created page … mx check -> [error] [CE1613] "The selected image 'Atlas_UI_Resources.Atlas_Icons.checkbox_checked' no longer exists." at Static image 'imgAll' (x3, one per state) Fixed, the same script reports all three at check time. With references that resolve, `check` passes and mxbuild reports no CE1613 — only CE0582, Mendix's own React-client deprecation of static images, which mendixlabs#1057's bug test already records as not an mxcli defect. It is a table now (imageRefProps), not a condition on one type name: the set grew and the condition did not, and `validate_widgets.go` already accepted these keys while DESCRIBE already emitted them, so only the resolver disagreed. Adding a widget that names an image means adding a row. Control: with the table reduced to the pluggable widget alone, the three symptom tests fail with the reported symptom (the reference accepted, `got 0 errors`) while both controls — a resolvable image under every spelling, and an unrelated `Image` property on a widget that is not in the table — still pass. Closes mendixlabs#1149 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ExDoCXFBfqq3R6j2qKx2Kf
…49-5kpac3 # Conflicts: # CHANGELOG.md
Workflow Groups (App Settings > Workflows > Groups) could not be created, listed or modified through mxcli, so a scripted project setup had to stop and open Studio Pro to define the buckets a user task's group targeting selects from. They are project settings — peers of UserEntity and DefaultTaskParallelism — and were missing from ALTER SETTINGS WORKFLOWS and DESCRIBE SETTINGS. Closes mendixlabs#272. alter settings workflows add [or modify] group 'Approvers' (Description: '…'); alter settings workflows modify group 'Approvers' (Description: '…'); alter settings workflows remove group 'Approvers'; show workflow groups; The four verbs match the LANGUAGE forms rather than the issue's proposed ADD/ALTER/DROP, because the two clauses share a grammar rule and the same reasoning: a group is identified by its name alone, so it is addressed the way a language's code is. ADD OR MODIFY is the upsert DESCRIBE SETTINGS emits, so a described project replays onto itself quietly. Pinned against real Mendix documents rather than a guess: - Settings$WorkflowGroup stores Name and Description and nothing else (modelsdk/gen, generated/metamodel and mendixmodelsdk 4.115.0 all agree), so Description is the only option and an unknown key is refused rather than written into a property the type does not declare. - The Groups typed-array marker is 2, not the 3 every other settings child list uses — measured on a blank 11.13.0 project, whose empty list is `Groups: [2]`. - The version floor is 11.2.0, not the 11.6 the issue quotes: both the type and the groups property read `introduced: "11.2.0"` in the Model SDK's StructureVersionInfo, and an 11.1.0 project's workflows settings part has no Groups key at all while an 11.13.0 one has it. Release notes date the feature's GA; the metamodel decides whether the document loads. Two things the implementation turns on: - The write is an overlay on the preserved raw part, so adding Groups to the model and the read path is not enough. Without the overlay call the executor reports "Added workflow group: Auditors (3 group(s))" and writes nothing, with mx check clean either way — the shape the enabled-language list already went through. A stored element that did not decode is refused rather than dropped (ADR-0005). - A group's element $ID is its identity in the runtime database. Booting the app materialises one system$workflowgroup row per entry whose modelguid is that $ID read as a .NET GUID (measured on 11.13.0: stored bytes 7c5fc4cf05c3394fa6718a3d4603e9a5, row cfc45f7c-c305-4f39-a671-8a3d4603e9a5), so a description edit updates the row in place instead of orphaning every group membership under a perfectly valid model. Verified end to end on Mendix 11.13.0: exec writes the document, mx check is 0 errors, the booted runtime materialises both groups, a replay is elided (no unit mtime moves) and describe -> exec round-trips including quote escaping. Controls recorded: deleting the one overlay call reproduces the silent no-write verbatim; reverting the option key from identifierOrKeyword to IDENTIFIER fails the visitor test with "mismatched input 'Description' expecting IDENTIFIER" — Description is an MDL keyword, so the feature's only option was a parse error. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TDVJRtXZmaXQGENThpcmDV
fix(pages): resolve image references, and validate what is inside a placeholder block
…ules
`retrieve $AccountList from Administration.Account sort by
System.Language.Code asc;` — MDL that `mxcli describe` had just emitted —
passed `mxcli check` and was refused by `mxcli exec`:
sort by attribute 'System.Language.Code' does not belong to entity
'Administration.Account'
Mendix stores a sort over an association as an AttributeRef naming the far
entity plus an EntityRef of EntityRefSteps. DESCRIBE emits only the
attribute's qualified name — MDL has no spelling for the hop — so replaying
a described retrieve has to re-derive the step.
inferSortEntityRefSteps made three assumptions that hold only when the hop
starts on the retrieved entity in its own module: it searched one domain
model (the retrieved entity's), matched only associations whose parent was
the retrieved entity itself, and qualified the association it found with the
retrieved entity's module. Administration.Account reaches System.Language
through System.User_Language, which is declared on the ancestor System.User
and stored in the System module — all three are wrong there.
It is now a generalization-chain walk that looks each ancestor up in its own
module and qualifies the association with the module storing it. The
destination end is matched with entityIsSubtypeOf rather than by equality,
since an association may point at a specialization of the entity that
declares the attribute.
Measured on a blank 11.12.3 app:
fixed exec ✓, describe → exec is a fixed point, mx check 0 errors,
stored hop reads System.User_Language → System.Language
fix reverted check ✓, exec fails with the reported message
hop dropped exec ✓, mx check CE7247 "Cannot sort on attribute
'System.Language.Code'. Attribute 'System.Language.Code' is
not an attribute of entity 'Administration.Account'."
An attribute of an entity that is neither in the chain nor one hop away is
still refused, in the unit tests and in the real project.
Known residue, recorded in the finding and in the code: where several
associations reach one entity, DESCRIBE cannot say which was stored and the
nearest ancestor's first hop wins. Spelling the hop needs grammar.
Fixes mendixlabs#1152
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NuVi6vytSqoqAtZK8YzvD2
The inference fix restored the reported script but left the round trip
lossy. `describe` emitted only the sort attribute's qualified name, so
replaying it had to re-derive the hop — and where two associations reach the
same entity there is nothing to derive it from.
Measured on 11.12.3, with Order_ShipTo and Order_BillTo both Order → Address
(an ordinary shape, not a corner case). A microflow sorting by the BILLING
address, run through describe → exec:
stored hop before: MyFirstModule.Order_BillTo
describe emits: sort by MyFirstModule.Address.City asc
stored hop after: MyFirstModule.Order_ShipTo
mx check is 0 errors on both sides. The app sorts by the wrong address and
nothing in the toolchain says so.
A sort column now takes an association path — one `/` per hop, the last
segment names the attribute — in a microflow retrieve and in a page
datasource alike:
retrieve $Orders from Sales.Order
sort by Sales.Order_BillTo/Sales.Address.City asc;
listview lv (datasource: database from Sales.Order
sort by Sales.Order_BillTo/City asc)
`qualifiedName (SLASH qualifiedName)*` is the shape MDLCatalog.g4 already
uses for Association/Entity, and `Assoc/Attr` is how DataGrid2 columns and
dynamictext params navigate, so this adds no new idiom. Inference stays as
the fallback: every script written before keeps working.
Two reads were missing, not one. sortItemsFromRaw read AttributeRef.Attribute
and skipped AttributeRef.EntityRef, and the page reader did the same — so the
hop was written and never read back, and DESCRIBE could not have emitted it
even with a spelling in hand. Fixing only the microflow half would have
shipped a describer that emits the path for microflows and silently drops it
for pages.
A hop that does not resolve, or does not start at the entity in hand, is
refused rather than written: a step with an empty DestinationEntity makes the
project unopenable rather than merely wrong.
Measured, both halves, with controls:
fixed mx check 0 errors; describe → exec replays every sort to
"Unchanged" (the write elided — the rebuild is identical)
hops ignored the named Order_BillTo is stored as Order_ShipTo
hop dropped, mx check CE7247 "Cannot sort on attribute
microflow 'System.Language.Code' …"
hop dropped, mx check CE7247 "… is not an attribute of entity
page 'MyFirstModule.Order'." at Sort bar of list view 'lvOrders'
Refs mendixlabs#1152
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NuVi6vytSqoqAtZK8YzvD2
feat(settings): manage workflow groups via MDL
… did not
The mendix-vega-charts skill pack stated that "same-origin requests carry
the session cookie, so an endpoint authenticated by session is reachable
from a chart on a page of the same app without any token handling". The
cookie does go; it is not sufficient. A URL-fed Vega-Lite spec therefore
drew axes and a full legend with zero data points and no error, because
Vega reads the 401 body as an empty dataset.
Measured on a freshly built 11.14.0 app, one URL, four requests:
no credentials 401
session cookie only 401
session cookie + X-Csrf-Token 200, correct payload
basic auth 200, correct payload
Rows 2 and 3 differ in exactly one header, and the 401 body is byte-identical
to the one reported. Through vega's own loader against that running app:
0 marks / 3 axes / no error without the token, 1 mark with it — the reported
symptom, reproduced end to end.
The docs fix is the reported ask, but a doc saying "the widget must supply
the header" over a widget that does not is half a fix, so the pack's widget
now installs a loader that attaches the token — to same-origin requests
ONLY. The token authenticates this session against this app, and the
same-origin decision resolves the URI against the page rather than testing
it for a scheme: a protocol-relative //elsewhere.example/rows.json carries
no scheme, so a scheme test reads it as relative and hands another host a
working session credential.
- widget/src/csrf.ts: isSameOrigin / readCsrfToken / withCsrfHeader,
pure and non-mutating (vega reuses one options object across a spec's
fetches, so writing a header into it would leak the token to later URLs)
- widget/test/csrf.test.ts: 9 cases, node's own runner over .ts, no deps
- make check-skill-pack-js + a CI step: nothing else in this repo compiles
a pack's widget, so logic shipped there was ungated
- two guards on the vendored pack in cmd/mxcli/skillpacks_test.go: a pack
documenting a relative-URL data source must mention X-Csrf-Token, and a
widget attaching the header must resolve the uri with new URL()
Controls, each run with the fix reverted:
- guard stubbed to pre-fix behaviour: "X-Csrf-Token missing: Mendix
answers this request 401", 4 of 9 cases fail
- the issue's own suggested regex in place of the origin resolution:
4 of 9 fail, including the protocol-relative leak
- both Go guards reverted: both fail, naming the file
Also verified: the modified widget typechecks clean under --strict against
the real vega / vega-embed / react types.
Fixes #574
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwJzSGaDx7vzTE2g4QN7kz
…087) #562. `create or modify entity` rebuilds the entity from the statement, so an attribute a LATER script added is deleted when the earlier script is re-run on its own. The reporter hit it with a calculated attribute: the entity is created in 01-domain-core.mdl and the attribute added in 03-logic.mdl, because the microflow it calculates by does not exist until then. Re-running slice 01 alone removed it, and the loss surfaced two slices later as CE1613 on a page — an error naming the page, never the script. The issue asks for two things. exec's warning already shipped (findings #24) and does fire; measured on a real 11.6.6 project re-running the reporter's slice 01, it prints the attribute by name, so the report predates it. The second ask did not exist: `mxcli check … -p app.mpr --references` printed "Check passed!" and said nothing, which is the one command that runs BEFORE anything is written. CheckEntityMemberDrops joins the catalog-backed tier of `check` as MDL087, a warning ("modify to this shape" is a legitimate intent; the defect was the silence, and the run still exits 0). It is deliberately NOT the same computation as the exec-time warning: - NET over the whole script, not per-statement. A script that rebuilds an entity and then `alter entity … add attribute`s the members back loses nothing — and that is the idiomatic full-script order, so a per-statement port would warn on every correct script. - Intent is tracked, not inferred. `drop attribute`, `rename attribute` and `drop entity` produce the same before/after diff as the accident. droppedEntityMembers now delegates to the shared entityMemberSet comparison rather than keeping a second hand-written diff: the audit system fields and an omitted `extends` are covered once, and an audit pseudo-type is a flag rather than an attribute on both sides. Reporting order is the entity's own attribute order, so the exec warning's output is unchanged. Controls recorded, both directions: - guard stubbed → 4 of 10 tests fail with the reported symptom ("got 0 violation(s)"), the silence tests still pass - net/intent logic removed → TestMDL087_ExplicitRemovalIsSilent fails on 3 of 4 spellings while the positive test still passes, so the positive test alone proves nothing Verified end to end on a real project: silent before, MDL087 after, with the concatenated slices and an explicit `drop attribute` both silent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011WGX3LgaNzHcnpAoHRzRZx
A textTemplate property took literal text only, so a caption written as
treenode tn (headerType: 'text', headerCaption: 'Name', ...)
passed check, exec and `mx check` and rendered the word "Name" on every
node. The companion the report reached for did not exist:
x widget `tn` (treenode) has no property `headerCaptionParams`
-- did you mean `headerCaption`? [MDL-WIDGET01]
`<Name>Params` was already the convention an object-list ITEM used for its
text templates (mendixlabs#956); it stopped at the item boundary. At the widget level
the engine read parameters from one place, the widget-wide `contentparams:`
-- a single list for every template on the widget, which cannot say "this
caption binds Name and that one binds Remarks". Three gaps:
1. resolveMapping's "TextTemplate" case consulted only GetContentParams().
2. A texttemplate mapping addressed by its def.json SOURCE name (an Image's
`ImageUrl:`, schema key `imageUrl`) fell through to the default branch,
which set no parameters at all -- not even contentparams, so mendixlabs#928's fix
only ever covered the schema-key spelling.
3. allowedWidgetProperties did not know the companion, so writing it was an
MDL-WIDGET01 error and exec refused the whole script.
Each text template now takes its own `<Name>Params`, under whichever
spelling the template itself was written (schema key, alias, or source
name); `contentparams:` stays the fallback and `'{AttrName}'` is
untouched. An orphaned companion -- parameters beside a literal caption --
is reported as MDL-WIDGET21 instead of being dropped in silence, and
DESCRIBE emits the companion so describe -> exec keeps the binding
(it round-tripped to a bare `{1}`, which re-executes into CE0720).
Measured on a blank Mendix 11.14.0 project, mxbuild 11.14.0, with the
built-in pluggable Image standing in for the TreeNode -- it has the same
shape (two text templates) and needs no download:
pre-fix check -> MDL-WIDGET01 x4 on imageUrlParams /
alternativeTextParams and their source-name spellings;
exec refuses, nothing written.
silent control: companions deleted, one shared
`contentparams: [{1} = PictureUrl]` left -- check,
exec and `mx check` (0 errors) all clean, and BOTH
stored ClientTemplates bind PictureUrl, so the alt
text renders the URL. Valid, buildable, wrong.
fixed check -> Check passed! exec -> Created page
mx check -> 0 errors
stored BSON -> imageUrl={1}[PictureUrl],
alternativeText={1}[Name]
describe -> exec -> "Unchanged page" (round trip stable)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DXYNJwiutu5AjxLmG7Fgsu
… spell The entity body authors exactly two rule types — Required from `not null` and Unique from `unique`. RegEx and Range are authored by a separate `create validation rule` statement and have no spelling in the entity body at all, but the merge listed ValidationRules among the fields the statement is authoritative about, so every rewrite overwrote the whole list and deleted them. The visible symptom was churn rather than loss, because the scripts that hit it re-create the rule in the next statement: an idempotent script reported `Modified entity: X` + `Created RegEx validation rule on X.Attr` on every run forever. Measured on the v1 fixture, the unit came back the same size (1,368 bytes) with 62 bytes differing, all of them element $IDs under ValidationRules/1. Splitting the two statements says which one moves — the entity rewrite ALONE takes the unit from 1,368 to 947 bytes. The loss is the real defect, and it is silent. Measured on a real 11.14.0 project, `create or modify entity` adding one attribute deletes the entity's RegEx rule and `mx check` reports "The app contains: 0 errors" — a re-created, or absent, rule is a valid model either way. Carrying an unreadable rule (MaxLength, EqualsTo) matters for a second reason: the model has no payload type for either, so it is UpdateEntity's guard-don't-drop refusal that must see it. Dropping it here skipped the guard entirely and turned "mxcli will not rewrite this entity" into a silent loss. A rule is dropped only on positive evidence that this rewrite removed its attribute — the stored entity owned one of that name and the rebuilt one does not. "Not among the declared attributes" is the wrong predicate and wrong in the direction that loses data, since an entity's rules can name inherited members that never appear in its own Attributes list (the trap pruneMemberAccessesForDroppedAttributes already paid for). ValidationRules is now in neither category of the field drift guard, so entityFieldsMergedWithStored is the third, and listing a field there is the deliberate act of taking it out of that guard's reach. `create validation rule` also reports through ReportMutation now, so a statement whose write was elided says Unchanged like every other one. Refs: #556 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWZjWZQhk3cNQCcy1Z2xzH
Several `create or modify` handlers are implemented as delete + create under the preserved unit ID rather than as an update. canon.Reconcile lives in updateUnit, so those writes never reached it and the rebuild's fresh element $IDs went straight to disk. Measured on a real 11.14.0 project, two consecutive identical runs of one `create or modify rest client` statement: the 1,128-byte unit came back the same size with 143 bytes differing, in 9 runs, every one an element $ID — /BaseUrl/$ID, /Operations/1/$ID, its Method, Path, Headers, Parameters and ResponseHandling. Feeding those two stored versions straight to canon.TransplantIDs makes them byte-identical, so the policy was never wrong here. It was never called. This is CLAUDE.md's "a new write path means wiring it to canon.Reconcile" in the shape that hides: the new path is not a new function, it is insertUnit reached by way of deleteUnit. deleteUnit now remembers what it removed (bounded at 1024 entries, past which the carry degrades to today's behaviour) and insertUnit reconciles a re-insert against it. The carry cannot elide — the row and the file are already gone — so a recreate that put back exactly what it removed also restores _Transaction.LastTransactionID, which both the delete and the insert bumped. That second half is load-bearing: with only the byte carry, every row of the SQLite db was logically identical and the .mpr still showed as modified, because that one UUID had moved. `create or modify rest client` is also fixed at its own layer, which is better than relying on the safety net: when the statement leaves the service where it is, it now calls UpdateConsumedRestService and the write is elided outright. Delete+create survives only for a folder move, which lives in the unit's row rather than in its contents. Deferring the delete to the write point is also strictly safer than doing it while scanning — building the service from the AST can fail, and the old code had already deleted the stored one by then. Measured end to end on 11.14.0, three consecutive re-runs of an identical statement: `Modified rest client` and 2 changed files every time before, `Unchanged rest client` and a clean tree after. A real edit (changing an operation's path) still lands, still reports Modified, and still builds at 0 errors. Refs: #556 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWZjWZQhk3cNQCcy1Z2xzH
One idempotent script covering both named instances of #556. Running it twice must leave the project byte-identical. The control is a binary built with both fixes stashed out, run on the same project: 3 changed files on every re-run, and `mx check` reporting "The app contains: 0 errors" each time — which is why the version-control diff was the only signal this class ever produced. Refs: #556 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWZjWZQhk3cNQCcy1Z2xzH
…g it
`CREATE OR MODIFY VIEW ENTITY` that changed ONLY the OQL printed
Unchanged view entity: Mod.Totals
while `describe entity` showed the new query stored. Changing the attribute
list as well reported Modified correctly, which is why it hid.
A view entity's OQL lives in a separate DomainModels$ViewEntitySourceDocument
unit, and the executor DELETED that unit and INSERTED a fresh one on every
write. Two costs, measured:
- `ReportMutation` downgrades the verb when writes were offered and none
landed, but the counters live at the update choke points and an InsertUnit
is not one. The domain-model unit was offered and correctly elided, the OQL
write was invisible, and the report believed the half it could see.
- the unit was replaced under a fresh GUID on EVERY run, including a
byte-identical one, so an MDL-generated project could never come back clean
in version control. #556 counted these units.
WriteViewEntitySourceDocument keeps the stored unit's id and goes through
UpdateRawUnit, which reconciles against what is stored (ADR-0008): an identical
query is elided, a changed one lands and is counted, duplicates are still
cleared. Create and update now share one encoder so they cannot drift into
writing different documents for the same query.
Counting InsertUnit instead would have swapped a false "Unchanged" for a false
"Modified" — measured: a byte-identical re-run re-minted the unit id, so every
view-entity statement would have reported Modified forever. That measurement is
why the fix is the reconcile wiring rather than the counter.
Verbs on the bug-test, in order, all four correct:
Created / Modified / Modified (OQL only) / Unchanged (identical re-run)
Unit id now stable across an identical AND a changed re-run; mxbuild 11.14.0
reports 0 errors on the result. Control: with the old delete-then-create
restored inside the new method, the test fails with the reported symptom —
"a changed OQL landed no counted write (Written stayed 0)".
Residual, deliberately not chased here: any content reaching storage through
InsertUnit is still invisible to the elision check, so a statement whose only
landing write is a NEW unit can be mis-reported. MoveUnit carries a comment
explaining it was counted for exactly this reason; insert and delete were
missed. Noted in #583 rather than changed unmeasured.
Closes #583
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L2aYb3zDscezm874CDTH6R
TreeNode and Timeline — the widgets the issue was filed against — do have a
generic describe path: every pluggable widget outside isKnownCustomWidgetType's
list of nine goes through extractExplicitProperties. That path read AttributeRef
and PrimitiveValue only, so EVERY text-template property of those widgets was
absent from `describe page` whether it was bound or literal, and describe -> exec
dropped the caption outright.
Measured on a blank 11.14.0 project, before this commit:
treenode tnProduct (
advancedMode: false, headerType: 'text', openNodeOn: 'headerClick',
hasChildren: true, startExpanded: false, animate: true,
showIcon: 'left', animateIcon: true
)
Ten primitives and no caption — which is why it looked complete. After:
treenode tnProduct (
DataSource: database from Bug575.Product, advancedMode: false,
headerType: 'text', openNodeOn: 'headerClick',
headerCaption: '{1}', headerCaptionParams: [{1} = Name], ...
)
and the same for a Timeline's title / description. Both pages round-trip:
describe -> exec reports "Unchanged page", mx check 0 errors.
Also corrects the bug test, which used the built-in Image on the stated
grounds that the reported widgets needed a Marketplace download. They do not:
TreeNode.mpk and Timeline.mpk ship in a blank app's widgets/ and their defs are
generated by `mxcli init`. The test now exercises all three — the Image earns
its place because its def.json names the mappings by SOURCE (`ImageUrl`) where
the TreeNode's names them by schema key, and the two take different branches of
resolveMapping.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DXYNJwiutu5AjxLmG7Fgsu
fix(executor): resolve a retrieve's sort hop across ancestors and modules
MDL031's pass-through length rule refused EVERY declaration of a column taken
from a System.* string attribute — including the one that builds — and
suggested the one that does not.
Measured on mxbuild 11.14.0. System.User.Name is String(100), not the 200 the
field report and my own issue text both assumed:
declared mxcli check (before) mx check
String(200) refused CE6770
String(100) refused 0 errors <- the correct one
String passed CE6770
The inferred length was 0 because the System metadata carries no lengths (0 of
115 attributes, #584), and the rule treated 0 as a length to match rather than
as "not known". formatDataTypeForMDL then substituted a hardcoded String(200)
for the unknown length, so the message refused String(200) and prescribed
String(200) in one sentence — and on this attribute that value is precisely
what mxbuild rejects.
passthroughStringLengthMismatch now declines a source length of 0. The rule
still fires wherever mxcli has the length, with its original message and exact
advice, verified end to end:
attribute 'MeterName': declared as String(200) but pass-through column
'm.MeterName' inherits length 100 from source attribute Mod.Meter.MeterName
— Mendix requires an exact length match (CE6770 …).
Fix: change to 'MeterName: String(100)'
The hardcoded String(200) in formatDataTypeForMDL is untouched: a DERIVED
string column is String(200) by rule whatever its source, so the two
type-mismatch suggestion sites want it. Only the pass-through branch, where the
number belongs to the source, is affected.
The trade, stated rather than hidden: mxcli no longer catches a wrong length
over a System.* attribute. It did not really catch one before — it refused
everything, which is why the reporter rewrote their column as
cast(u.Name as string) — but the gap is now honest, and #584 closes it by
supplying the lengths.
Control: with the unknown case forced back through the old wording, the test
fails with the reported message verbatim, including "inherits length 0" and the
invented suggestion. The known-length case is asserted alongside it, so a build
that simply deleted the rule fails too.
Closes #585
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L2aYb3zDscezm874CDTH6R
fix(check): report the members a script removes from the project (MDL087)
fix(view entity): write the OQL document in place instead of replacing it
fix: re-exec is identity-stable for validation rules and REST clients
fix: session-auth OData needs X-Csrf-Token, and the vega pack said it did not
fix(view entity): do not judge a pass-through length mxcli does not know
`meta.SystemAttrDef` declared a `Length` and not one of the 115 String
attributes populated it, so `systemAttrType` built every System string as
`StringAttributeType{Length: 0}` — unlimited — and every length comparison
against a System attribute was made against 0.
The cost was not cosmetic. A view entity selecting `u.Name` from System.User
could not be declared in any way that both passed check and built:
declared mxcli check (before) mxcli check (after) mx check 11.14.0
String(100) refused passes 0 errors
String(200) passed (after #585) refused, "use 100" CE6770
String passed refused, "use 100" CE6770
The reporter worked round it with `cast(u.Name as string)`, where the derived
column rule fixes the length at 200 whatever the source.
The lengths are measured, from the System module's domain model inside the
`deployment/model/model.mdp` mxbuild writes — the model the runtime builds the
System tables from, so the same number CE6770 is decided by. That is one
`--target=deploy` for all 115, against ~40s per attribute for the view-entity
probe; the Model SDK does not carry them at all (its gen/ describes metamodel
types) and the modeler's copy is inside Mendix.Modeler.Core.dll.
Whether the numbers are version-specific was measured rather than assumed:
10.24.4.77222 and 11.14.0 agree on all 216 attributes they share, types and
lengths alike, so one table serves every supported version.
- testdata/system_string_lengths.txt holds the measurement, with the command
that regenerates it.
- TestSystemStringLengths holds SystemEntities to it in BOTH directions, so a
String attribute added without a measurement fails rather than defaulting to
unlimited. That is what makes a stored 0 Mendix's "unlimited" rather than
"nobody looked" — 46 of the 115 genuinely are unlimited.
- TestSystemStringLengthsUpdate is the measuring half: point it at a built
model.mdp with -mdp, add -update to rewrite the golden.
Control: reverting the populated lengths fails both new tests with the reported
symptom ("System.User.Name: read as String(0), mxbuild builds it as
String(100)"). End to end on mxbuild 11.14.0, the String(100) view entity is 0
errors and the String(200) one is CE6770, matching mxcli's new verdicts.
The 29 System members 11.14.0 has that this table does not are left alone: a
member absent from the target version is CE1613, which is a version-gating
question a length never is.
Closes #584
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L2aYb3zDscezm874CDTH6R
Bind a pluggable widget's textTemplate to an attribute (#575)
Measure the System module's String lengths (#584)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Syncs
ako/mxcli:mainintomendixlabs/mxcli:main— 31 commits, 15 of them non-merge, sincea909abc7(docs(changelog): release v0.23.0). The fork is 0 behind.Grouped by theme; each bullet is one fix with the measurement that settled it.
Sorting a retrieve over an association — #1152
d614dbf).retrieve … sort by System.Language.Code asc— MDLmxcli describehad just emitted — passedcheckand was refused byexecwith "does not belong to entity 'Administration.Account'". The hop isSystem.User_Language, declared on the ancestorSystem.Userand stored in the System module; the resolver searched one domain model, matched only associations whose parent was the retrieved entity itself, and qualified with the wrong module. Now a generalization-chain walk. Control: a build that derives the hop and does not write it is mxbuild CE7247.fe5a740). Inference cannot be made correct where two associations reach one entity. Measured on 11.12.3 withOrder_ShipTo/Order_BillTo(bothOrder → Address): a microflow sorting by the billing address came back fromdescribe → execsorting by the shipping one,mx check0 errors both sides.sort by Sales.Order_BillTo/Sales.Address.City ascnow says which — in a microflow retrieve and a page datasource alike. Two readers were dropping the stored hop, not one.Pages — references that nothing resolved, properties that nothing bound
placeholderblock (79186ce0). A page's widgets live in two AST fields and all three validators were given only the bare body, so every reference insideplaceholder X { … }— microflow, nanoflow, page, snippet, entity, image — was validated by nothing. Not an edge shape: it is what mxcli's own skills, bug-tests and DESCRIBE output write.f5961faa, SelectionHelper custom states cannot include StaticImageViewer widgets via MDL #1149).widgetRefCollectorkeyed the image reference on widget type, sostaticimage'sImageanddynamicimage'sDefaultImageresolved to nothing —check --referencespassed and the build failed CE1613.95c1841f, Consolidate DataGrid builder paths: datagrid keyword vs pluggablewidget engine #575). AheaderCaption: 'Name'rendered the literal word "Name" on every node, and the<Name>Paramscompanion the report reached for did not exist at widget level.839e6c28, Consolidate DataGrid builder paths: datagrid keyword vs pluggablewidget engine #575). Every pluggable widget outside the list of nine went through a path readingAttributeRefandPrimitiveValueonly, so TreeNode's and Timeline's text templates were absent fromdescribe pageanddescribe → execdropped the caption.Rewrites that silently dropped or churned
create or modify entitycannot spell (240c5aa6). The entity body authors Required and Unique only; RegEx and Range come from a separate statement and were listed among the fields the rewrite is authoritative about, so every rewrite deleted them. The visible symptom was churn, because the scripts that hit it re-create the rule next statement — the loss is silent.3ed95a43). Severalcreate or modifyhandlers are delete + create under the preserved unit ID, andcanon.Reconcilelives inupdateUnit, so those writes never reached it. Two identical runs of onecreate or modify rest clientdiffered in 143 bytes, every one an element$ID. This is CLAUDE.md's "a new write path means wiring it tocanon.Reconcile" in the shape that hides — the new path isinsertUnitreached by way ofdeleteUnit.65d79818). Changing only the OQL printedUnchanged view entitywhiledescribe entityshowed the new query stored.568f7be0). One idempotent script covering both instances of Re-exec is not identity-stable: nested GUIDs are re-minted, and three unit types are replaced under a new unit GUID ako/mxcli#556; the control is a binary with both fixes stashed out — 3 changed files on every re-run,mx check0 errors each time, which is why the version-control diff was the only signal this class ever produced.System-module metadata
616e2d23, fix #583: parse StringAttributeType.Length across BSON numeric widths #584).meta.SystemAttrDefdeclared aLengthand none of the 115 String attributes populated it, so every System string was built unlimited and every length comparison was made against 0. A view entity selectingu.NamefromSystem.Usercould not be declared in any way that both passedcheckand built.1237b5cd). MDL031 refused every declaration of a column taken from aSystem.*string attribute — including the one that builds — and suggested the one that does not.System.User.NameisString(100), not the 200 both the field report and the issue text assumed.Other
71851f74, CREATE OR MODIFY ENTITY drops attributes added by a later ALTER without saying so ako/mxcli#562).create or modify entityrebuilds from the statement, so an attribute a later slice added is deleted when the earlier slice is re-run alone; the loss surfaced two slices later as CE1613 on a page, naming the page and never the script. Now caught bycheck … --references, the one command that runs before anything is written.832e9c80, feat: Manage Workflow Groups via MDL #272).alter settings workflows add|modify|remove group '…'andshow workflow groups— a scripted project setup no longer has to stop and open Studio Pro to define the buckets a user task's group targeting selects from.X-Csrf-Token(10d6892b). The mendix-vega-charts skill pack said the session cookie was sufficient; it is not, and Vega reads the 401 body as an empty dataset, so a chart drew axes and a full legend with zero points and no error. Measured on 11.14.0, one URL, four requests: cookie only → 401, cookie +X-Csrf-Token→ 200