Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .claude/skills/fix-issue/findings/mdl-backend.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,10 @@
{"area": "mdl/backend", "date": "2026-09-29", "symptom": "describe -> exec, CREATE OR MODIFY or an ALTER that rebuilds the document turns an API-exported document Hidden: enumerations, pages, layouts, rules, view-entity OQL source documents, import/export mappings, JSON structures, published and consumed REST services, scheduled events, workflows, database connections, business event services, data transformers, queues, regular expressions and agent-editor documents. The run reports success, mx check is clean; the module's public surface silently shrinks. A workflow's own `export level API` clause was a no-op on create and on rewrite.", "cause": "Each rewrite converter builds a fresh document and writes ExportLevel as a constant (\"Hidden\"), or passes the semantic model's value where the executor itself filled in \"Hidden\" (mappings, database connection, business events), and the unit is replaced wholesale. MDL has no export-level spelling for most of these kinds, so describe cannot print it and the executed script cannot restore it. workflowToGen ignored wf.ExportLevel entirely. The round-trip harness could not see it: every document in TestApp and PedApp is Hidden, the constant itself.", "file": "mdl/backend/modelsdk/export_level_carry.go", "fix": "One byte-level carry, keepStoredExportLevel(unitID, contents): replaces only the top-level ExportLevel element of the freshly encoded rewrite with the stored value, copying every other element verbatim, and never adds the key. Wired into every Update path that writes ExportLevel (UpdateEnumeration/Rule/Layout/ImportMapping/ExportMapping/JsonStructure/PublishedRestService/ConsumedRestService/DataTransformer/DatabaseConnection/BusinessEventService, writeCustomBlob update, WriteViewEntitySourceDocument update; page via carryStoredPageHeader). Kinds with an MDL spelling (workflow, scheduled event, queue, regular expression) use keepStoredExportLevelUnlessSet: an authored level wins. workflowToGen now writes orDefault(wf.ExportLevel, \"Hidden\").", "insight": "A fixture-driven round trip is blind to any constant that happens to equal every fixture value: 775 TestApp documents round-tripped while 10 kinds hid API documents. Set the subject to the non-default value first (here: patch ExportLevel to API on the working copy) and run both the plain describe output and an edited one, because an elided unchanged write passes a converter that still writes the constant. Carrying at the encoded-bytes level covers gen-typed, newElem-built and hand-serialized writers with one helper, where a gen setter per converter would have needed three mechanisms.", "refs": ["ako/mxcli#816", "ako/mxcli#801", "ako/mxcli#812"], "test": "mdl/backend/modelsdk/issue816_export_level_test.go (TestUpdatePaths_KeepStoredExportLevel, 18 kinds); mdl/roundtrip/export_level_test.go (TestTestAppExportLevelSurvivesRoundTrip, -tags integration)"}
{"date": "2026-09-30", "area": "mdl/backend", "symptom": "ako/mxcli#859 review of PR #864: after the built comparison landed, changing or adding `show page M.P with title = 'X'` in a `create or modify microflow` reported \"Unchanged microflow\" and wrote nothing, under mdl 0 and mdl 1 (main spliced it). Nothing warned.", "cause": "builtAsStored compares the declared flow and the stored flow both READ BACK through the codec, so any property the reader drops compares equal whatever either side holds. The ShowFormAction reader never read FormSettings.TitleOverride. Probing encode(built) against encode(readback(built)) over mdl-examples found the reader also dropped ExclusiveSplit/LoopedActivity ErrorHandlingType and a REST call's bound output variable (ResultHandling.ResultVariableName -> RestCallAction.OutputVariable), plus CallWebServiceAction (#861). Before the built comparison such a loss was a visible phantom re-splice; after it, a silently dropped edit.", "fix": "ReadBackMicroflow/ReadBackNanoflow re-encode what they read back and refuse (error -> statement diff, the pre-#859 path) when it is not the document first written, $IDs aside (sameWritten). The reader now reads TitleOverride, the split's and loop's ErrorHandlingType, and a bound REST call's OutputVariable, so those flows keep matching.", "insight": "A comparison made on both sides through the same lossy reader cannot see what the reader loses; the lost property becomes a change that is never written. When equality is decided after a decode, prove the decode lossless for the value at hand (write it again and compare bytes) and fall back when it is not. The probe that found the fields: diff encode(x) with encode(decode(encode(x))) over every mdl-examples flow.", "issue": "ako/mxcli#859", "file": "mdl/backend/modelsdk/microflow_readback.go, mdl/backend/modelsdk/microflow_read_actions.go, mdl/backend/modelsdk/microflow.go, mdl/roundtrip/flow_idempotent_shapes_test.go"}
{"date": "2026-09-30", "area": "mdl/backend", "symptom": "ako/mxcli#843 (rehearsal M2): under mdl 1, `create or modify nanoflow … returns Boolean as $Done` over a nanoflow stored without a return variable refuses \"the stored document has no ReturnVariableName property … set it in Studio Pro\"; the same statement on a microflow reports \"set: ReturnVariableName\".", "cause": "mfmutator.SetHeader refuses any stated header key the stored document lacks (a key the project version does not declare makes the document unopenable). mxcli's nanoflow writer omits ReturnVariableName when the statement has no `as $Var`, while the microflow writer always writes it on 10+, so only nanoflows hit the refusal.", "fix": "Optional mfmutator.PropertyDeclarer on Deps; the codec deps answer from the metamodel version data (type, then Microflows$MicroflowBase; ReturnVariableName is 10.12+) against the project version, and SetHeader inserts the key after its predecessor in the encoder's order. No answer (MCP, unknown version) keeps the refusal.", "insight": "A refusal keyed on 'the stored document lacks the key' conflates 'this version has no such property' with 'the writer left it out'; the metamodel version data separates the two. Studio Pro 11 stores ReturnVariableName on every nanoflow (PedApp: 13 of 13), so adding it matches what Studio Pro writes.", "issue": "ako/mxcli#843", "file": "mdl/backend/mfmutator/header.go"}
{"date": "2026-10-01", "area": "mdl/backend", "symptom": "mendixlabs/mxcli#1214: `alter page … { set Editable = true on lvRows }` printed \"Altered page\" and the list view stayed Editable = false, so its inputs rendered read-only.", "cause": "pagemutator's `editable` case wrote the value only when it was a string (the input-widget Always/Never enum). A list view's and data view's Editable is a BOOLEAN, so `true` fell through and returned nil — success with nothing stored. A lower-case string ('never') was stored verbatim.", "fix": "setWidgetEditableMut: the stored value's type decides the vocabulary (bool → true/false; string → canonical Always/Never, Conditional only via [expr]); anything else, or a widget with no Editable, is refused. EditableIf now also sets the Conditional enum, as CREATE does.", "insight": "A setter switch that type-asserts and returns nil on a miss is a silent no-op generator; read the STORED field's type first, it says which vocabulary the property has on this widget.", "issue": "mendixlabs/mxcli#1214", "file": "mdl/backend/pagemutator/mutator.go"}
{"date": "2026-10-01", "area": "mdl/backend", "symptom": "ako/mxcli#528 / mendixlabs/mxcli#293: a data view footer could not be edited — `replace footer1 with {…}` (describe's name), the name the script wrote, and `dvMain.footer` all reported not found.", "cause": "A data view footer is a REGION: its widgets live in the data view's FooterWidgets and the footer has no stored Name. The builder hoisted the children and discarded the name; describe invented `footer1`.", "fix": "`<dv>.footer` resolves as a region in ResolveAlterTarget/ResolvesTarget; INSERT INTO appends (turning ShowFooter on for an empty footer), REPLACE swaps the content (a describe-style `footer { }` block is unwrapped), DROP empties it; a not-found names the footer addresses. A name on a data view footer is MDL-DEPR005; describe prints `footer { }`. REPLACE may also reuse the names of the widgets it removes (ContainedWidgetNames).", "insight": "Same shape as scroll-container regions: a slot without a name is addressed positionally by its owner. Check the grammar's own doc comment too — it advertised `replace footer1`, a form that never resolved.", "issue": "ako/mxcli#528", "file": "mdl/backend/pagemutator/footerregion.go"}
{"date": "2026-10-01", "area": "mdl/backend", "symptom": "mendixlabs/mxcli#1247: `alter page … replace comboBox12 with { combobox … }` to add a sort reset the combo box's translated placeholder and readOnlyStyle; on TestApp's Studio Pro-authored Rules.BusinessRule_NewEdit it also turned the stored Editable \"Never\" into \"Always\" and an expression property's PrimitiveValue \"\" into \"false\". exec reported success, mx check stayed green.", "cause": "A pluggable widget is rebuilt from its template plus the MDL mappings, so every property MDL cannot state comes back at the template value. #830's passthrough is all-or-nothing and only for unchanged widgets.", "fix": "For one pluggable widget replaced by one of the same package, the executor also builds the STORED widget as describe prints it (baseline); pagemutator.mergeUnstatedPluggable keeps the stored Type and every stored property/field on which replacement and baseline agree, and grafts the differing values with their TypePointers re-aimed at the stored Type by key path. Anything that cannot be lined up falls back to the plain replace.", "insight": "'What the statement states' is measured by building the description of the stored widget beside the statement, not by listing mapped keys: a property that comes out equal from both builds is the template's, not the author's.", "issue": "mendixlabs/mxcli#1247", "file": "mdl/backend/pagemutator/pluggable_replace.go"}
{"date": "2026-10-01", "area": "mdl/backend", "symptom": "mendixlabs/mxcli#1247 on a DataGrid 2: restating TestApp's Studio Pro-authored dataGrid2_1 with one column caption changed silently turned itemSelectionMethod \"rowClick\" into \"checkbox\", reset onClickTrigger and rebuilt every column's and filter's unmapped properties — the combo-box fix's merge did not apply and REPLACE fell back to the template rebuild.", "cause": "remapTypePointers re-aimed every TypePointer in the grafted columns value, including those of the nested filter widgets, which point into their OWN Type; no counterpart -> merge refused -> plain replace. And even when it applied, the columns list was grafted whole, so one changed caption reset every column's unstated properties.", "fix": "remapTypePointers leaves a nested CustomWidgets$CustomWidget as built; an object-list property whose objects line up one for one with the baseline's is merged object by object (properties indexed by their property type's path), so only the stated property of the stated column changes.", "insight": "A fallback that restores the old behaviour is the old bug: test the merge on the most nested pluggable widget available (DataGrid 2 with filters), not only on a flat one, and count the fallbacks.", "issue": "mendixlabs/mxcli#1247", "file": "mdl/backend/pagemutator/pluggable_replace.go"}
{"date": "2026-10-01", "area": "mdl/backend", "symptom": "mendixlabs/mxcli#698: over --mcp, `call microflow … on error rollback|continue` was created with PED's default error handling; a custom handler's error edge was written as an ordinary sequence flow. No error either way.", "cause": "mapMicroflowAction built every action map without errorHandlingType (only notify carried it), and buildFlowDocContent wrote every flow alike — PED's SequenceFlow constructor has no property for an error-handler flow (ped_get_schema, Studio Pro 11.14).", "file": "mdl/backend/mcp/microflow.go (mapObjectTree, buildFlowDocContent)", "fix": "carryErrorHandlingType writes the action's ErrorHandlingType (read by reflection); Custom/CustomWithoutRollBack and IsErrorHandler flows are refused with a message to run without --mcp.", "test": "TestMapObjectTree_CarriesErrorHandlingType, TestMapObjectTree_RefusesACustomErrorHandler", "insight": "ped_get_schema answers 'can PED express this' in one call; check it before mapping a property, and refuse what it cannot hold."}
{"date": "2026-10-01", "area": "mdl/backend", "symptom": "ako/mxcli#628: `move entity A.Parent to B; move entity A.Child to B;` both report success, then Studio Pro cannot open the project: System.AggregateException: The given key '<guid>' was not present in the dictionary.", "cause": "MoveEntity scanned only sourceDM.AssociationsItems(); an existing cross-association (created by the first move) was invisible, so moving its FROM entity left its ParentPointer naming an element absent from the unit, and moving its TO entity left Child naming the old module. Cross-unit damage, so the #1119 write guard cannot see it.", "fix": "association_move_cross.go: own cross-associations (FROM = moved entity) travel to the target, or become a plain DomainModels$Association there when the TO entity is already in it (raw transform: $Type, Child -> ChildPointer binary id, connection points added, GUID passed through); a cross-association in the target naming the entity becomes plain; any other module's cross-association naming it is re-pointed. MovedAssociation.SameModule lets the executor report the plain conversions apart.", "insight": "Assert on the stored documents (every ParentPointer/ChildPointer resolves in its own unit, every cross Child resolves to an entity in the named module); mx check is a weak signal for this class. The four sequences (to-then-from, from-then-to, travel, re-point) are distinct shapes, not one.", "issue": "ako/mxcli#628", "file": "mdl/backend/modelsdk/association_move_cross.go"}
{"date": "2026-10-01", "area": "mdl/backend", "symptom": "ako/mxcli#627: the fluent API's AttributeModifier.Apply() (Backend.UpdateAttribute) on a Studio Pro-authored attribute is refused with \"refusing to write unit …: 1 element(s) kept their $ID but would be written with a different GUID (DomainModels$Attribute)\"; without the #1119 guard it would re-mint the GUID and drop the column on the next deploy.", "cause": "UpdateAttribute rebuilds the attribute with attributeToGen (raw == nil) and carried only the $ID, so the codec's EmitGUID default wrote GUID = $ID. No MDL statement reaches it — every ALTER ENTITY form goes through UpdateEntity, which has the carry — so it was found by enumerating the converter's call sites, not by a repro.", "fix": "UpdateAttribute carries the stored attribute's raw bytes and export level onto the rebuild via carryStoredAttribute, the helper now shared with carryAttributeIdentity. Attribute -> Attribute keeps $Type, so SetRaw suffices.", "insight": "Test on PedApp (GUID != $ID); an mxcli-created attribute re-mints the same value and cannot fail. The unfixed code fails the test via the write guard's refusal, which is the observable symptom on main.", "issue": "ako/mxcli#627", "file": "mdl/backend/modelsdk/domainmodel_alter.go"}
Loading
Loading