Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
94a3b6b
mdl: R2 brackets for integration documents (properties in ( ), childr…
ako Sep 28, 2026
e03a059
mdl: alter microflow fragments are begin … end (R2)
ako Sep 28, 2026
ab9c5eb
docs: move skills, docs, examples and syntax help to the R2 brackets
ako Sep 28, 2026
c380d61
mdl: alter sets properties in create's ( Key: value ) list; colons wh…
ako Sep 28, 2026
85737c9
R5: bare workflow and widget expressions, one constant reference, rev…
ako Sep 28, 2026
8af8432
docs: R5 bare expressions, one constant reference, canonical revoke (…
ako Sep 28, 2026
6f0b022
mdl/visitor: keep MDL-DEPR074's begin/end apart from touching text
ako Sep 28, 2026
e39520b
mdl/deprecation: structural rewrites are noun phrases after "Rewrite …
ako Sep 28, 2026
7eabdd2
fix(grammar): a bare timer delay does not take the 'non' of the next …
ako Sep 28, 2026
3125713
test(executor): the workflow round trip expects a bare decision condi…
ako Sep 28, 2026
3621c63
mdl/visitor: R3 rewrites keep a comment between the tokens they touch…
ako Sep 28, 2026
11a9ff5
mdl/executor: renamed design property suggests the canonical styling …
ako Sep 28, 2026
a32681c
docs: settings and OData alter syntax in the R3 property list (#751)
ako Sep 28, 2026
bf8c1ad
R6: one verb per job — show->describe, drop, call rest service (#755,…
ako Sep 28, 2026
bf0d1d3
fix(grammar): a misspelt statement keyword is an error at the word (R7)
ako Sep 28, 2026
6b919b6
feat(mdl): session commands are REPL-only under mdl 1 (R7)
ako Sep 28, 2026
0e46128
roundtrip: an upgrade use reported as unrewritable may stay in the ou…
ako Sep 28, 2026
d513348
R6: mxcli's own messages and commands use the canonical verbs (#755)
ako Sep 28, 2026
c2f5b16
feat(mdl): documentation, folder and caption have one place each (R9)
ako Sep 28, 2026
9b2abb5
docs(mdl): R9 canonical metadata placement in skills, syntax and docs
ako Sep 28, 2026
d235290
fix(workflow): create or modify carries what describe cannot print (#…
ako Sep 28, 2026
a91eb69
fix(odata client): create or modify carries the stored document (#743)
ako Sep 28, 2026
b3b2671
fix(domain model): write every NoGeneralization system-attribute flag…
ako Sep 28, 2026
4505530
fix(external entity): refuse a declared type the service does not pub…
ako Sep 28, 2026
7e5c5ea
describe workflow, consumed odata service and external entity as crea…
ako Sep 28, 2026
ddea174
test(roundtrip): describe never emits R9's deprecated spellings
ako Sep 28, 2026
684d719
docs: changelog for R7 and R9 (#755)
ako Sep 28, 2026
3320ec5
chore(findings): helpStatement catch-all swallowed misspelt keywords …
ako Sep 28, 2026
bab5499
fix(describe): a constant or JSON structure documentation with */ kee…
ako Sep 28, 2026
b518ae7
fix(grammar): an unknown statement word is an error only under mdl 1 …
ako Sep 28, 2026
6d681d2
docs(skills): drop the PRIVATE flag the database-connections skill ta…
ako Sep 28, 2026
2fa0d67
test(odata client): pin the stored-document carry in UpdateConsumedOD…
ako Sep 28, 2026
a4e0176
Merge branch 'c6-778' into c6-776
ako Sep 28, 2026
13bd4fd
Merge branch 'c6-776' into c6-777
ako Sep 28, 2026
0e7ecc0
merge: one CHANGELOG entry for the #753 grant change (#777's revision)
ako Sep 28, 2026
c2d2cdf
Merge branch 'c6-777' into c6-779
ako Sep 28, 2026
230fab5
Merge branch 'c6-779' into c6-780
ako Sep 28, 2026
dd977ee
Revert "fix(grammar): an unknown statement word is an error only unde…
ako Sep 28, 2026
aa31e09
Merge branch 'c6-780' into c6-782
ako Sep 28, 2026
6f89f4d
test: describe writes a workflow activity's caption as caption (R9) i…
ako Sep 28, 2026
b35cce8
Merge branch 'c6-780' into c6-782
ako Sep 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .claude/skills/design-mdl-syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Does an existing pattern cover this? If yes, extend it. Don't invent new syntax.
```
New feature: "image collections"
Existing pattern: create/alter/drop/list/describe
design: create image collection Module.Name (...)
design: create image collection Module.Name { image X ( File: '…' ) }
describe image collection Module.Name
list image COLLECTIONS [in module]
```
Expand Down
3 changes: 3 additions & 0 deletions .claude/skills/fix-issue/findings/mdl-backend.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,6 @@
{"area": "mdl/backend", "date": "2026-09-26", "symptom": "describe → exec of a nanoflow detaches every annotation from its activity (AnnotationFlows 3 → 0, so the next describe prints the notes as free-floating) and deletes ExportLevel, UseListParameterByReference and ReturnVariableName; MarkAsUsed is cleared. Rules drop their annotation flows too", "cause": "nanoflowToGen and ruleToGen never wrote ObjectCollection.AnnotationFlows (microflowToGen did), and nanoflowToGen never set the three header keys; nanoflowFromGen did not read ReturnVariableName or MarkAsUsed and the builder hardcoded MarkAsUsed false", "file": "mdl/backend/modelsdk/nanoflow_write.go, rule_write.go, microflow.go; mdl/executor/cmd_microflows_build.go", "insight": "Three flow flavours share the object/flow converters but each has its own top-level writer, and each dropped a different subset — grep every *ToGen for the same list loop, not just the reported one. For keys with no MDL spelling and unknown version floors (Nanoflow.ExportLevel, UseListParameterByReference), carry ONLY the keys the stored unit has: nothing was ever written on create, Studio Pro fills an absent key, and an undeclared one makes the unit unopenable", "refs": ["ako/mxcli#705"]}
{"area": "mdl/backend", "date": "2026-09-27", "symptom": "Running unchanged `describe user role` output (create or modify user role) rewrites Security$ProjectSecurity: /UserRoles[0] marker 2 -> 3; same for DemoUsers on a demo-user rewrite", "cause": "The encoder's default PartList marker is 3; Studio Pro writes 2 for ProjectSecurity.UserRoles and .DemoUsers (PedApp, TestApp, expr-checker agree). An untouched list passes through raw, so it only shows once an element in it is rewritten", "file": "mdl/backend/modelsdk/security_write.go", "insight": "A marker bug hides until the first rewrite of an element in that list; census the stored marker per (owner $Type, key) across Studio Pro fixtures and register it with RegisterPropertyListMarker", "refs": ["ako/mxcli#731"]}
{"area": "mdl/backend", "date": "2026-09-27", "symptom": "Any write of a domain model (even rewriting a sibling entity) drops LocalizeDate from every DateTime attribute in the unit; external entity rewrites also rewrite Source/Key/Parts marker 2 -> 3", "cause": "attributeTypeToGen emitted DateTimeAttributeType without LocalizeDate and attributeTypeFromGen never read it; Rest$ODataKeyPart had no registered list marker so the encoder default 3 applied", "file": "mdl/backend/modelsdk/domainmodel.go, domainmodel_write.go", "insight": "A whole-unit rewrite re-serializes every sibling from the struct, so a property the struct cannot hold is lost on EVERY entity of the module, not just the edited one. Absent LocalizeDate means Mendix's default true (mxcli wrote it absent for years), so the reader must map absent to true or the fix would unlocalize every mxcli-authored DateTime. Only a Studio Pro fixture with DateTime attributes (TestApp) shows it", "refs": ["ako/mxcli#743"]}
{"area": "mdl/backend", "date": "2026-09-28", "symptom": "create or modify consumed odata service over a Studio Pro client: UseQuerySegment true -> false, Icon, CustomLocationTemplate and the empty ApplicationId/EndpointId/CatalogUrl/proxy/microflow keys deleted, MetadataReferences [3] -> [0], ValidatedEntities [1] -> [0]", "cause": "consumedODataServiceToGen writes the document wholesale from a model that holds none of these; placeholders (UseQuerySegment false, ExportLevel Hidden) and addStrIf-omitted empties replaced what Studio Pro stored", "file": "mdl/backend/modelsdk/odata_write.go (carryStoredConsumedODataService)", "fix": "UpdateConsumedODataService lays the fresh document over the stored one: placeholder keys keep the stored value, an omitted optional key is written \"\" when stored (so a cleared value stays cleared), lists keep the stored marker", "insight": "When the model is a strict subset of the stored document, overlay onto the stored bytes rather than growing the model field by field; but an optional key the writer omits must become \"\" rather than be carried, or clearing it through alter silently stops working", "refs": ["ako/mxcli#743"], "test": "TestConsumedODataServiceRewrite_CarriesWhatTheModelDoesNotHold"}
{"area": "mdl/backend", "date": "2026-09-28", "symptom": "Rewriting a Studio Pro workflow drops the empty Flow and the PersistentId of every outcome that leads nowhere, writes RelativeMiddlePoint/Size \"\" where Studio Pro stores \"0;0\", and deletes an empty EventSubProcesses [2]", "cause": "The writer added an outcome's Flow only when non-nil (Workflows$Outcome.flow is Required), gave condition outcomes no PersistentId, and omitted an empty EventSubProcesses list because the property is version-floored (11.8)", "file": "mdl/backend/modelsdk/workflow_write.go (encodeWorkflow, outcomeFlowToGen)", "fix": "Always write an outcome Flow; PersistentId on every outcome; \"0;0\" layout; empty EventSubProcesses via MandatoryListMarkers, suppressed through Encoder.OmitKeys below 11.8 only when the workflow has none (MandatoryListMarkers now honours OmitKeys)", "insight": "Encoder.OmitKeys suppresses a key whatever it holds, so version-floor only the empty default: suppressing the key outright would drop declared sub-processes on a project whose version could not be read", "refs": ["ako/mxcli#743"], "test": "TestWorkflowToGen_EveryOutcomeStoresAFlow"}
{"area": "mdl/backend", "date": "2026-09-28", "symptom": "Every domain-model rewrite deletes MaybeGeneralization HasOwnerAttr/HasChangedByAttr/HasCreatedDateAttr/HasChangedDateAttr = false on Studio Pro entities (#721 B)", "cause": "entityToGen set only the true flags, for parity with the legacy serializer", "file": "mdl/backend/modelsdk/domainmodel_write.go", "fix": "Write all four flags", "insight": "Legacy-writer parity is not Studio Pro parity; the flags were the last getput loss of a Studio Pro external entity, which is why it surfaced in #743", "refs": ["ako/mxcli#721", "ako/mxcli#743"], "test": "TestEntityToGen_WritesEveryGeneralizationFlag"}
2 changes: 2 additions & 0 deletions .claude/skills/fix-issue/findings/mdl-executor.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -736,3 +736,5 @@
{"area": "mdl/executor", "date": "2026-09-27", "symptom": "Running an external entity's describe output (create or modify external entity, either form) turns each attribute's Rest$ODataMappedValue into a DomainModels$StoredValue (RemoteName/RemoteType/Filterable... gone), re-mints attribute $IDs on the `from odata client` form, and rewrites String(36) as unlimited", "cause": "Both rewrite paths rebuilt attributes from the AST, which has no spelling for the OData mapping; the `from odata client` path minted fresh IDs; describe external entity printed GetTypeName() (`String`) instead of formatAttributeType", "file": "mdl/executor/cmd_entities.go (carryStoredAttributeState), cmd_odata.go", "insight": "Carry per attribute by name what the statement cannot spell, in the shared merge, and enumerate every path that rebuilds attributes: there were two statements for one document kind", "refs": ["ako/mxcli#743"]}
{"area": "mdl/executor", "date": "2026-09-27", "symptom": "create or modify odata service over a Studio Pro service: EntitySet PageSize 10000 -> 0, entity sets reordered, member CanBeEmpty recomputed, ExportLevel deleted, AuthenticationTypes marker 1 -> 3", "cause": "The modify path rebuilt entity types/sets from the publish block (no spelling for PageSize without paging, set order, CanBeEmpty); the model had no ExportLevel; the writer used a marker-3 list copied from the legacy writer, not Studio Pro", "file": "mdl/executor/cmd_odata.go (carryPublishedEntityState), mdl/backend/modelsdk/odata_write.go", "insight": "Parity with the legacy writer is not evidence of what Studio Pro writes; measure markers on a Studio Pro fixture. A probe that runs describe output with the verb rewritten to create or modify (mdl/roundtrip TestTestAppCreateOrModifyProbe) shows what a verb switch would lose before switching it", "refs": ["ako/mxcli#743"]}
{"area": "mdl/executor", "date": "2026-09-27", "symptom": "describe microflow printed `validation feedback $O/A message '{1} \u2026';` with no template arguments; describe -> exec stored the feedback with none", "cause": "formatAction's ValidationFeedbackAction case never read TemplateParameters, although the reader (textTemplateFromGen) and the writer both carried them", "file": "mdl/executor/cmd_microflows_format_action.go (templateArgsClause)", "insight": "A text template has two halves, the text and its parameters; check every describe case that prints one prints both. Found while switching the template form to `with ({1} = \u2026)` (ako/mxcli#751)."}
{"area": "mdl/executor", "date": "2026-09-28", "symptom": "create or modify workflow of its own describe output renames Studio Pro's implicit activities: start1 -> Start, end1 -> End, an event sub-process end2 -> End2", "cause": "describe prints no name for the implicit start/end, branch ends, jumps or end-of-path markers, and the rebuild names them from captions", "file": "mdl/executor/workflow_carry_names.go", "fix": "Pair declared and stored flows structurally (named activities by name, unnamed ones by type ordinal, nested flows by position, event sub-processes by name) and carry the stored names before deduplication", "insight": "Carry before the CE0495 deduplication, not after, so the carried names take part in it; pair nested flows with nil slots kept, because the rebuild leaves an empty outcome's flow nil where the stored one is an empty Flow", "refs": ["ako/mxcli#743"], "test": "TestCreateOrModifyWorkflow_CarriesNamesDescribeCannotPrint"}
{"area": "mdl/executor", "date": "2026-09-28", "symptom": "create or modify external entity declaring OrderId: String(20) on an Edm.Int64 property keeps RemoteType Edm.Int64; mx check CE6616", "cause": "carryStoredAttributeState carries RemoteType by name regardless of the declared type", "file": "mdl/executor/external_entity_remote_type.go", "fix": "Refuse under mdl 1 (MDL-V1-REMOTETYPE), warn under mdl 0", "insight": "CE6616 compares against the service's $metadata, not the stored RemoteType, so deriving RemoteType from the declared type would not fix it; measured with mx check on a TestApp copy", "refs": ["ako/mxcli#764"], "test": "TestCreateOrModifyExternalEntity_RemoteTypeMismatchRefusedUnderMdl1"}
1 change: 1 addition & 0 deletions .claude/skills/fix-issue/findings/mdl-grammar.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,4 @@
{"area": "mdl/grammar", "date": "2026-09-23", "symptom": "`alter page M.P { set 'createFileAction' = microflow M.ACT_CreateFile on fileUploader1; };` (quoted or bare key) fails to parse: `line 2:37 extraneous input 'MyModule' expecting {DROP, ADD, SET, INSERT, REPLACE, '}'}` — a pluggable widget's NAMED action slot, writable on CREATE PAGE since #956, could only be retargeted by REPLACEing the whole widget", "cause": "`alterPageAssignment` special-cased `Action = actionExprV3` and sent every other key to `propertyValueV3`, which has no `microflow <name>` form. Below the grammar there was also no route: `SetWidgetProperty` would have stringified an action into `PrimitiveValue`, and `SetWidgetAction` writes the built-in click action, not a pluggable slot", "file": "`mdl/grammar/MDLParser.g4` (`alterPageAssignment`: `STRING_LITERAL|identifierOrKeyword EQUALS actionExprV3`), `mdl/visitor/visitor_alter_page.go` (keeps the author's key), `mdl/executor/cmd_alter_page.go` (`applySetPropertyMutator` routes any `*ast.ActionV3` not keyed `Action`), `mdl/backend/pagemutator/mutator.go` (`SetWidgetNamedAction`); tests `mdl/visitor/visitor_alter_page_named_action_test.go`, `mdl/backend/pagemutator/mutator_named_action_test.go`, `mdl/executor/alter_set_named_action_test.go`; example `mdl-examples/bug-tests/995-alter-page-set-named-action-slot.mdl`", "insight": "**Decide action-slot-ness by the stored PropertyType's `ValueType.Type == \"Action\"`, never by the presence of `Value.Action`** — every WidgetValue carries an Action (a NoAction by default) whatever its type, so a field-presence check would \"succeed\" writing into an Integer and change nothing. Unlike CREATE, ALTER has no datasource overlap to yield to (DataSource is its own alternative), so the action alternative goes before the scalar ones and `microflow M.X` parses straight to an action — no DataSourceV3 conversion. The check-time probe (validate_alter_set.go) dry-runs the same setter, so the wrong-type refusal surfaced at `check -p --references` for free. Measured on 11.12.1: set on a DataGrid 2 with Selection unset (slot hidden) also passed `mx check` at 0 errors — the CE0463 in #956's notes was 11.13.0, so ALTER does not yet run MDL-WIDGET10; don't assume either way without a build on the target version", "refs": ["mendixlabs/mxcli#995", "mendixlabs/mxcli#956"]}
{"area": "mdl/grammar", "date": "2026-09-24", "symptom": "`DROP PAGE IF EXISTS FieldService.Stub;` -> `line 1:13 extraneous input 'EXISTS' expecting the start of a statement`. No document-level DROP (35 alternatives: entity through folder) accepted IF EXISTS, so any script that dropped something was one-shot and the drop had to be deleted after its first run (ako/mxcli#531, ChipCoV3, 11.14.0).", "cause": "`dropStatement` in MDLParser.g4 never applied the existing `ifExists` rule; only sub-document drops (attribute, index, enum value) and the two security drops had it, each added one statement at a time with its own AST field and handler branch.", "file": "`mdl/grammar/MDLParser.g4` (dropStatement); `mdl/ast/ast_drop.go` (DropGuard embedded in 35 Drop*Stmt); `mdl/visitor/visitor_entity.go` (ExitDropStatement sets it once, via defer); `mdl/executor/registry.go` (Dispatch turns NotFoundError into a skip); `mdl/executor/validate.go` (check --references skips guarded drops); tests `mdl/executor/drop_if_exists_test.go`; example `mdl-examples/bug-tests/531-drop-if-exists.mdl`", "insight": "The per-statement recipe the previous IF EXISTS fixes used (field + handler branch) would have been 35 copies, and the next doctype would miss it. What made one central guard safe was a measurement, not an assumption: a table test driving every bare DROP at a missing target through the registry showed all 35 handlers already return mdlerrors.NotFoundError, and only at lookup, before any mutation — so Dispatch can key on that type and let every other error through. Keep that bare-form table test: it is what breaks if a new drop handler reports 'not found' with fmt.Errorf. Mock pitfall on the way: MockBackend's Get*ByQualifiedName defaults to (nil, nil), which the real backend never returns for a missing document — mirror its 'not found' error or the handler dereferences nil. And check --references resolved a drop's MODULE, so a guarded drop in a missing module passed exec but failed check until validateWithContext skipped it too."}
{"area": "mdl/grammar", "date": "2026-09-25", "symptom": "`create java action \u2026(EntityType: entity <String> not null, \u2026)` failed with \"mismatched input 'String' expecting IDENTIFIER\"; Studio Pro accepts a type parameter named after a primitive, so such an action could not be authored, and DESCRIBE of a Studio Pro-authored one printed `returns String`, which re-parses as the primitive.", "cause": "dataType's `ENTITY LESS_THAN IDENTIFIER GREATER_THAN` excluded keyword tokens, and the describer emitted type-parameter names bare where a bare keyword means something else.", "file": "mdl/grammar/domains/MDLDomainModel.g4, mdl/executor/cmd_javaactions.go (formatCodeActionTypeParameterRef)", "insight": "Two halves of one round trip: the declaration slot needed identifierOrKeyword, and the *reference* slots needed no grammar change at all \u2014 a quoted `\"String\"` already parses as a bare qualifiedName, which isTypeParamRef resolves, while unquoted `String` stays the primitive. So the fix on the output side is mdlIdent (which lexes the name with the real lexer) on every type-parameter name DESCRIBE prints. Prove it with a describe \u2192 exec into a second module \u2192 describe diff, not by eyeballing one DESCRIBE.", "refs": ["mendixlabs/mxcli#1183"]}
{"area": "mdl/grammar", "date": "2026-09-28", "symptom": "A misspelt statement keyword was not an error. `craete module Foo;` and `dorp entity M.E;` parsed cleanly and did nothing (0 statements, exit 0); `craete persistent entity Shop.Note (Text: string(200));` reported `extraneous input '('` at column 35 instead of at the typo.", "cause": "`helpStatement: IDENTIFIER (helpTopicWord (DOT? helpTopicWord)*)?` is a utilityStatement alternative, so ANY statement starting with an IDENTIFIER matched it, the rest of the words as the topic; ExitHelpStatement then built nothing for a word other than help/?/exit/quit. The rule is the grammar's catch-all (the #1025 comment already warned it swallows from other statements); a typo is just the case where there is no other statement.", "file": "`mdl/grammar/MDLParser.g4` (`@parser::members` IsHelpWord), `mdl/grammar/domains/MDLSettings.g4` (semantic predicate on helpStatement), `mdl/visitor/visitor_unknown_statement.go` (\"unknown statement 'x' — did you mean 'y'?\" when the parser is still in statement/program and ANTLR names the one word), test `mdl/visitor/visitor_session_commands_test.go`", "insight": "A catch-all alternative that the visitor filters (`switch word { case \"help\": … }`) is a silent no-op for everything it does not list: move the filter INTO the grammar as a predicate (`{IsHelpWord(p.GetTokenStream().LT(1).GetText())}?`), so the parser, not the visitor, rejects. The words stay IDENTIFIERs — reserving HELP/EXIT/QUIT as tokens would take them away as names everywhere a bare IDENTIFIER is accepted. The generated parser imports no `strings`, so fold case by hand in @members. With the predicate the error lands at the word as `no viable alternative at input 'craete'`, and the listener can rewrite exactly that message when the recognizer's context is StatementContext/ProgramContext. Proof: without the predicate the test reports 3 of 4 inputs parsing to zero statements.", "refs": ["ako/mxcli#755", "mendixlabs/mxcli#1025"]}
6 changes: 3 additions & 3 deletions .claude/skills/implement-mdl-feature.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ Based on the BSON structure, design MDL syntax that:

Example design for REST call:
```sql
$response = rest call get 'http://api.example.com/data'
$response = call rest service get 'http://api.example.com/data'
header 'Content-Type' = 'application/json'
header Accept = 'application/json'
auth basic $username password $password
Expand All @@ -156,7 +156,7 @@ Add examples to `mdl-examples/doctype-tests/`:
create microflow RestExamples.SimpleGet()
returns string as $response
begin
$response = rest call get 'https://api.example.com/data'
$response = call rest service get 'https://api.example.com/data'
timeout 30
returns string;
return $response;
Expand Down Expand Up @@ -597,7 +597,7 @@ Expected: Valid MDL output that could be re-parsed.
create microflow Test.RestExample()
returns string as \$R
begin
\$R = rest call get 'http://example.com' timeout 30 returns string;
\$R = call rest service get 'http://example.com' timeout 30 returns string;
return \$R;
end;"

Expand Down
Loading
Loading