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
4 changes: 2 additions & 2 deletions .claude/commands/mxcli-dev/proposal.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Ask the user:
5. **Does this touch BSON serialization?** (reading or writing Mendix documents)

If the user isn't sure about version or BSON, help them find out:
- Version: check `reference/mendixmodellib/reflection-data/` or Mendix release notes
- Version: check the Mendix release notes
- BSON: check if similar features exist in `mdl/backend/modelsdk/*_read.go` or `*_write.go`

### Phase 2: BSON Investigation (if applicable)
Expand All @@ -51,7 +51,7 @@ in Studio Pro that are painful to debug.
```

3. **Document the BSON structure** in the proposal:
- Storage name (`$Type` field) — verify against `reference/mendixmodellib/reflection-data/`
- Storage name (`$Type` field) — verify against `modelsdk/gen/<domain>/types.go` and the dumped document
- All fields with types and observed values
- Any fields that use Mendix-internal IDs (pointers to other documents)
- Note any counter-intuitive naming (like Parent/Child pointer inversion)
Expand Down
5 changes: 2 additions & 3 deletions .claude/skills/debug-bson.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,8 @@ The principle: for each `$type`, ALL instances must have the **exact same set of
### Version-Specific Properties

Some properties only exist in certain Mendix versions. Before adding a property to a BSON writer:
1. Check `reference/mendixmodellib/reflection-data/` for the property definition
2. Check `min_version` if present
3. Test with `mx diff` self-diff on the target version
1. Check a Studio Pro-authored document **of the target version** for the property
2. Test with `mx diff` self-diff on the target version

**Example**: `IsReusableComponent` exists in `Projects$ModuleImpl` in newer Mendix but NOT in 11.6.4. Writing it → crash.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "cmd/mxcli", "date": "2026-10-03", "symptom": "mendixlabs/mxcli#1286: on Windows the first `run --local` / `test --local` for a new Mendix version fails with `linking runtime into mxbuild cache: symlink <home>\\.mxcli\\runtime\\11.12.5\\runtime <home>\\.mxcli\\mxbuild\\11.12.5\\runtime: A required privilege is not held by the client.`; hand-made `New-Item -ItemType Junction` links let the run proceed", "cause": "ensureMxBuildRuntimeSibling and ensurePADFiles linked a directory with os.Symlink; on Windows CreateSymbolicLink needs SeCreateSymbolicLinkPrivilege (Developer Mode or an elevated token), which an ordinary user lacks (ERROR_PRIVILEGE_NOT_HELD, 1314). A directory junction needs no privilege", "file": "`cmd/mxcli/docker/linkdir_windows.go` (linkDir); call sites `cmd/mxcli/docker/runlocal.go` (ensureMxBuildRuntimeSibling), `cmd/mxcli/docker/build.go` (ensurePADFiles)", "fix": "build-tagged linkDir(target, link): POSIX = os.Symlink; Windows = os.Symlink first, and only on errors.Is(err, syscall.Errno(1314)) fall back to `cmd /c mklink /J` (HideWindow, absolute target), returning mklink's output plus the original symlink error if that fails too. mklink rather than FSCTL_SET_REPARSE_POINT keeps golang.org/x/sys an indirect dependency", "insight": "Linux CI cannot see this: os.Symlink always works there, and a Windows CI runner or a Developer-Mode machine passes too, so the bug only shows on an ordinary Windows user account. The repo's own TestEnsureMxBuildRuntimeSibling already failed on such a machine (the #897 family: Windows-only failures nobody saw) - when a package has long-standing 'pre-existing' Windows failures, read their messages for a privilege/OS cause before writing them off. Safety property to test, not assume: download.go and build.go os.RemoveAll the mxbuild/runtime cache dirs that now contain a junction; Go (1.23+) reports a junction as ModeIrregular (a symlink is ModeSymlink) and RemoveAll deletes the junction itself, not the runtime behind it - measured with go1.26.6 on Windows, target file intact. The test's temp dir has a space in its name because mklink goes through cmd.exe quoting. Wrong turn to skip: do not touch the os.Symlink calls in check_copy.go / marketplace/refcache.go, they recreate existing symlinks while copying trees (a different concern)", "refs": ["mendixlabs/mxcli#1286", "#897"], "test": "`cmd/mxcli/docker/linkdir_test.go` (read through link, Stat follows, RemoveAll removes link not target, existing link path errors, Windows fallback proven when os.Symlink returns 1314 and the link is ModeIrregular); `TestEnsureMxBuildRuntimeSibling` now passes on Windows"}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area":"mdl/executor","date":"2026-10-09","symptom":"`Tooltip:` on an actionbutton/linkbutton passes check and exec but is never stored; describe omits it; `alter page … set (Tooltip: …) on btn` refused with `property \"Tooltip\" is not a property of this built-in widget`","cause":"Every hop but one carried it: grammar parsed TOOLTIP, staticWidgetKnownProps listed it (so MDL-WIDGET07 stayed quiet), pages.ActionButton had the field and widget_write.go wrote it — but buildButtonV3 never read w.Properties[\"Tooltip\"], so the codec always wrote an empty Texts$Text. describe had no reader for it, and setRawWidgetPropertyMut had no `tooltip` case so it fell to the pluggable setter's refusal","file":"mdl/executor/cmd_pages_builder_v3_widgets.go, mdl/executor/cmd_pages_describe_parse.go, mdl/backend/pagemutator/mutator.go","insight":"Being on the known-props allow-list proves only that SOME builder consumes the key, not that THIS widget's builder does — the list is a cross-widget union by design. For a silent drop, grep the struct field's writers (`\\.Tooltip =`): the codec wrote it and nothing assigned it. Tooltip is a Texts$Text (not a ClientTemplate like Caption), so the ALTER path reuses setTranslatableText on the stored empty Texts$Text; mx check on 11.12.5 accepts a translated tooltip with 0 errors","refs":["mendixlabs/mxcli#1307","mendixlabs/mxcli#1263"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/executor", "date": "2026-10-09", "symptom": "After the .mpr changed, `show references` / `search` / `show callers` / `show impact` on a source-mode catalog failed with \"statement timed out after 5m0s\" on a 3,032-document app (build stopped at document 1,110), and every retry did the same: the cache stayed stale. Explicit `refresh catalog` worked", "cause": "ensureCatalog rebuilds a stale cache at its cached source mode (the #1081 never-narrow fix) inside the calling statement, and Execute's wall-clock guard counted that build. REFRESH CATALOG is exempt from the default guard (#651) by statement type; an implicit rebuild of the same catalog inside a ShowStmt was not. The build is only saved on completion, so a guard firing mid-build leaves the cache exactly as stale as before, guaranteeing the next call repeats it", "file": "`mdl/executor/untimed_work.go` (per-statement tracker of untimed sections, chained for EXECUTE SCRIPT), `mdl/executor/executor.go` (Execute: timer re-armed by the excluded time instead of context.WithTimeout; executeTimeoutError names an interrupted rebuild), `mdl/executor/cmd_catalog.go` (ensureCatalog marks its build untimed); tests `catalog_rebuild_timeout_test.go`; repro `mdl-examples/bug-tests/1329-implicit-catalog-rebuild-timeout.mdl`", "insight": "An exemption keyed on statement TYPE misses the same work reached implicitly: #651 exempted REFRESH CATALOG, and the identical build behind SHOW REFERENCES stayed capped. Exempt the work, not the statement — mark the bounded section where it runs and have the guard subtract it. Wrong turn to skip: building full instead of source for full consumers — the never-narrow guard then refuses to save it and the rebuild repeats on every call (the very thrash ensureCatalog's source upgrade was added to stop). A guard that discards half-finished work must say so, or each retry reads as a fresh failure. Reproduce with a test hook that lengthens the build and a lowered default limit; the fixture builds in under a second", "refs": ["mendixlabs/mxcli#1329", "mendixlabs/mxcli#651", "mendixlabs/mxcli#1081"], "ce": [], "rules": []}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area":"mdl/executor","date":"2026-10-09","symptom":"`retrieve $u from $task/System.owner;` (or System.changedBy) passed `check` and `exec`; mxbuild then failed `[CE0136] \"Retrieve object must specify the 'Entity' property.\"` — with or without `owner: AutoOwner` on the entity. Issue mendixlabs/mxcli#1358 (11.12.2)","cause":"owner/changedBy are HasOwner/HasChangedBy flags, not modelled associations. mxbuild resolves the NAME in an AssociationRetrieveSource (System.Owner is CE1613 instead, so lookup works) but derives no target entity from it, so the RetrieveAction has none. Nothing mxcli can write in that source builds","file":"`mdl/executor/validate_microflow_retrieve_system_assoc.go` (MDL-RETRIEVE02, exec-enforced in `execEnforcedMicroflowRules`)","insight":"The issue proposed writing `Entity: System.User` — there is no such property: AssociationRetrieveSource has only StartVariableName + AssociationId, and CE0136's 'Entity' is the entity Studio Pro DERIVES from the association. Measure alternatives with mx check before designing a writer fix: `[System.owner/M.Task = $task]` on System.User is CE0161 (system associations do not traverse in reverse in XPath), but `retrieve $u from System.User where [id = $task/System.owner] first;` builds clean for owner and changedBy. So the fix is a project-free refusal naming that rewrite, not a builder change. Not measured for nanoflows (rule is microflow-only)","refs":["mendixlabs/mxcli#1358","mdl-examples/bug-tests/1358-retrieve-system-owner-association.mdl","mdl-examples/bug-tests/1358-retrieve-system-owner-association.fail.mdl"],"ce":["CE0136"],"rules":["MDL-RETRIEVE02"]}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"area": "sdk/mpr", "date": "2026-04-21", "symptom": "`TypeCacheUnknownTypeException` in Studio Pro", "cause": "Wrong `$type` storage name in BSON write", "file": "`sdk/mpr/writer_microflow.go`", "insight": "Check the storage name table in CLAUDE.md; verify against `reference/mendixmodellib/reflection-data/`"}
{"area": "sdk/mpr", "date": "2026-04-21", "symptom": "`TypeCacheUnknownTypeException` in Studio Pro", "cause": "Wrong `$type` storage name in BSON write", "file": "`sdk/mpr/writer_microflow.go`", "insight": "Check the storage name table in CLAUDE.md; verify against `modelsdk/gen/<domain>/types.go`"}
92 changes: 36 additions & 56 deletions .claude/skills/implement-mdl-bson
Original file line number Diff line number Diff line change
Expand Up @@ -65,63 +65,51 @@ Use `dump-mpr` to answer:

### Find Type Definition

The vendored metamodel types in `modelsdk/gen/<domain>/` carry the storage names.
`types.go` holds each type's `$Type` and its BSON field names; `refs.go` holds
the reference kind of each pointer property.

```bash
# Search for a type in reflection data
grep -A 50 '"Microflows\$Microflow"' reference/mendixmodellib/reflection-data/11.6.0-structures.json | head -60
# $Type storage name and BSON field names of a type
grep -n -A 12 'SetTypeName("Microflows\$Microflow")' modelsdk/gen/microflows/types.go
```

### Key Metamodel Fields

```json
{
"Microflows$Microflow": {
"qualifiedName": "Microflows$Microflow",
"storageName": "Microflows$Microflow", // ← Use for $Type in BSON
"properties": {
"objectCollection": {
"name": "objectCollection",
"storageName": "ObjectCollection", // ← Use for BSON field name
"typeInfo": {
"type": "ELEMENT",
"elementType": "Microflows$MicroflowObjectCollection",
"kind": "PART" // ← Determines reference format
}
}
}
}
}
The `init<Type>()` constructor lists one property per BSON field, with the
storage name as the first argument:

```go
o.SetTypeName("DataTypes$ListType") // ← $Type in BSON
o.entity = property.NewByNameRef[element.Element]("Entity", "DomainModels$Entity") // ← BSON field "Entity"
```

`modelsdk/gen` is pinned to a Mendix release and does not know about anything
added after it. For anything newer, or whenever gen and a document disagree,
dump a Studio Pro-authored document; **the document wins**.

### Reference Kinds

| Kind | BSON Format | Go Type |
| Kind (`codec.RefKind`) | BSON Format | Go Type |
|------|-------------|---------|
| `PART` | Embedded document | Struct pointer |
| `BY_ID_REFERENCE` | Binary UUID | `model.ID` (serialize as BSON Binary) |
| `BY_NAME_REFERENCE` | String | `string` (qualified name like "Module.Entity") |
| `LOOKUP` | String | `string` |
| embedded part | Embedded document | Struct pointer |
| `RefById` | Binary UUID | `model.ID` (serialize as BSON Binary) |
| `RefByName` | String | `string` (qualified name like "Module.Entity") |
| `RefByNameList` | Array of strings | `[]string` |

### Looking Up Reference Kind in Metamodel
### Looking Up Reference Kind

**Critical:** Always check the `kind` field in the metamodel before implementing references.
**Critical:** Always check the reference kind before implementing references.
Getting this wrong causes "Value cannot be null" or similar errors in `mx check`.

```bash
# Search for property definition
grep -A 20 '"entity"' reference/mendixmodellib/reflection-data/11.6.0-structures.json | grep -A 10 'DataTypes\$ListType'
# Reference properties of a type
grep -n -A 4 '"DataTypes\$ListType"' modelsdk/gen/datatypes/refs.go
```

Example discovery - `DataTypes$ListType.entity`:
```json
"entity" : {
"name" : "entity",
"storageName" : "Entity",
"typeInfo" : {
"type" : "ELEMENT",
"elementType" : "DomainModels$Entity",
"kind" : "BY_NAME_REFERENCE" // ← This is the critical field!
}
}
Example discovery - `DataTypes$ListType.Entity`:
```go
codec.DefaultRefRegistry.RegisterRefs("DataTypes$ListType", []codec.RefMeta{
{Prop: "Entity", Kind: codec.RefByName, Target: "DomainModels$Entity"}, // ← the critical field
})
```

This means `Entity` must be serialized as `"MyModule.MyEntity"` (string), NOT as a binary UUID.
Expand All @@ -131,24 +119,17 @@ This means `Entity` must be serialized as `"MyModule.MyEntity"` (string), NOT as
- `mx check` error: `"Value cannot be null. (Parameter 'value') at Mendix.Modeler.DataTypes.EntityType.set_EntityId"`
- Fix: Changed to string qualified name format

You can also check `reference/mendixmodellib/src/mpr/mprJsonConversion.js` for the TypeScript implementation:
```javascript
case "BY_NAME_REFERENCE":
case "LOCAL_BY_NAME_REFERENCE":
return value || ""; // Just passes string through, not binary
```

### Audit Similar Types

When you fix a BY_NAME_REFERENCE bug in one type, **immediately audit all similar types**.
When you fix a by-name reference bug in one type, **immediately audit all similar types**.

Example: When we fixed `DataTypes$ListType.entity`, we should have checked:
- `DataTypes$ObjectType.entity` - Same bug! Also BY_NAME_REFERENCE
- `DataTypes$EnumerationType.enumeration` - Same pattern! Also BY_NAME_REFERENCE
- `DataTypes$ObjectType.entity` - Same bug! Also `RefByName`
- `DataTypes$EnumerationType.enumeration` - Same pattern! Also `RefByName`

```bash
# Find all entity references that might be BY_NAME_REFERENCE
grep -B 5 '"BY_NAME_REFERENCE"' reference/mendixmodellib/reflection-data/11.6.0-structures.json | grep -E '"(name|storageName)"'
# Find all by-name references to entities
grep -rn 'RefByName, Target: "DomainModels\$Entity"' modelsdk/gen/*/refs.go
```

### Context-Specific Type Interpretation
Expand Down Expand Up @@ -682,8 +663,7 @@ MxCheck tests only fail if errors are **related to newly created artifacts**:

## Reference Files

- Metamodel structures: `reference/mendixmodellib/reflection-data/11.6.0-structures.json`
- BSON conversion logic: `reference/mendixmodellib/src/mpr/mprJsonConversion.js`
- Metamodel types: `modelsdk/gen/<domain>/types.go` (storage names) and `refs.go` (reference kinds)
- BSON mapping docs: `docs/05-mdl-specification/10-bson-mapping.md`
- Existing implementations: `sdk/domainmodel/`, `sdk/mpr/writer.go`
- mx tools: `reference/mxbuild/modeler/mx` (check, create-project, build)
16 changes: 8 additions & 8 deletions .claude/skills/test-mdl.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ The type cache does not contain a type with qualified name DomainModels$index

**Cause**: Using `qualifiedName` instead of `storageName` for `$type` field.

**Fix**: Check `reference/mendixmodellib/reflection-data/<version>-structures.json` for the correct `storageName`.
**Fix**: Use the `$Type` from the `SetTypeName(...)` call in `modelsdk/gen/<domain>/types.go`, or from a Studio Pro-authored document.

### System.ArgumentNullException

Expand All @@ -128,9 +128,9 @@ System.ArgumentNullException: value cannot be null. (parameter 'AttributeId')

**Cause**: Wrong reference format. Using UUID for BY_NAME_REFERENCE or vice versa.

**Fix**: Check metamodel `typeInfo.kind` for the property:
- `BY_NAME_REFERENCE` → qualified name string (e.g., "Module.Entity.Attr")
- `BY_ID_REFERENCE` → binary UUID
**Fix**: Check the property's reference kind in `modelsdk/gen/<domain>/refs.go`:
- `RefByName` → qualified name string (e.g., "Module.Entity.Attr")
- `RefById` → binary UUID

### Enumeration Not Displayed

Expand Down Expand Up @@ -158,11 +158,11 @@ go run ./examples/debug_bson/main.go mx-test-projects/test1-go-app/test1-go.mpr
### Check Metamodel

```bash
# find type definition
grep -A 30 '"DomainModels\$Index"' reference/mendixmodellib/reflection-data/11.6.0-structures.json
# find type definition (storage name and BSON field names)
grep -n -A 12 'SetTypeName("DomainModels\$EntityIndex")' modelsdk/gen/domainmodels/types.go

# find property reference kind
grep -B 5 -A 10 '"storageName" : "Attribute"' reference/mendixmodellib/reflection-data/11.6.0-structures.json
# find property reference kinds
grep -n -A 6 '"DomainModels\$IndexedAttribute"' modelsdk/gen/domainmodels/refs.go
```

## Test Project
Expand Down
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ coverage.out
coverage.html
reference/mendix-repl/
reference/mxbuild/
reference/mendixmodellib/
reference/mdl-grammar/
reference/mxbuild-11.6.0/
/reference/
Expand Down
Loading
Loading