Skip to content

Sync ako/mxcli → upstream: TimeRegistration findings #51–#56 - #807

Merged
ako merged 25 commits into
mendixlabs:mainfrom
ako:main
Jul 30, 2026
Merged

Sync ako/mxcli → upstream: TimeRegistration findings #51–#56#807
ako merged 25 commits into
mendixlabs:mainfrom
ako:main

Conversation

@ako

@ako ako commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Syncs the latest batch of fixes from ako/mxcli:main (validated against a real Mendix project, ako/mxcli-timeregistration). Every fix was verified against mxbuild 11.12.1 (mx check → 0 errors), and each ships with a regression test, an mdl-examples/bug-tests/ repro, and a fix-issue.md symptom row.

ALTER PAGE — attribute bindings on non-database datasources (#55, closes remaining #49)

  • Inserting/replacing a data-bound widget into a list bound from association or datasource: microflow/nanoflow bound the widget's attribute to the wrong entity (or nothing) — mxcli check passed but the build failed CE1613 / CE0402.
  • Association: read the destination off the last EntityRefStep of the IndirectEntityRef. Microflow/nanoflow: the source stores no entity (it's the flow's return type), so the mutator now reports the governing flow's qualified name (EnclosingDataSourceFlow, with a nearer non-flow source shadowing an outer flow) and the executor resolves its return entity.

Microflow check false-positives

DESCRIBE round-trip

Diagnostics / docs

  • Daily Digest: 2026-03-29 #51: create association on re-run now names the idempotent form (create or modify association …) in its error.
  • Daily Digest: 2026-03-30 #56: documented (with a bug-test) that a widget show_page's $currentObject argument is intentionally not stored — Mendix infers it, and storing it explicitly triggers CE0115 — so the describe→exec round-trip is functionally lossless.

Platform-only, no code change: #50 (daysBetween sign) and #52's dateTime()-literal restriction (already surfaced by MDL046).

claude and others added 25 commits July 29, 2026 14:50
…am-pr)

Generating a cross-fork compare URL to merge ako/mxcli:main into
mendixlabs/mxcli:main was a recurring manual task — mendixlabs/mxcli is
not in tooling scope, so the PR can't be opened via API and we hand the
user a prefilled compare link instead.

- scripts/upstream-pr-link.sh: URL-encodes a title + Markdown body into a
  ?title=&body= compare URL. Defaults to ako/mxcli:main -> mendixlabs:main;
  --commits auto-builds the body from a git range; --body-file reads a
  hand-written body (stdin via -). Handles newlines/backticks/ampersands.
- .claude/commands/mxcli-dev/upstream-pr.md: contributor slash command that
  wraps the script (draft title/body, generate link, present plain-text
  fallback, note the out-of-scope caveat).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
….9 (FINDINGS #39)

A workflow whose flow contains a "call microflow" activity was written with the
on-disk $Type `Workflows$CallMicroflowTask`. Both checkers passed (mxcli check ✓,
mx check → 0 errors), but on an 11.9+ project the runtime refused to load the
ENTIRE model at boot:

    Failed to load model: ... Class 'Workflows$CallMicroflowTask' could not be found

Mendix 11.9 (WOR-2802) split MicroflowBasedActivity into CallMicroflowActivity +
AIAgentTaskActivity, renaming the on-disk $Type. Evidence from the cached
toolchains: the 11.6.3 modeler knows only CallMicroflowTask; the 11.10 modeler
carries both (the old one marked "Removed due to code refactoring ... WOR-2802")
plus a conversion routine; the 11.10+ runtime metamodel jars know only
CallMicroflowActivity. The 11.9 boundary matches the existing HasOwner→HasOwnerAttr
domain-model gate.

Fix: emit CallMicroflowActivity for projects >= 11.9 and keep CallMicroflowTask for
older ones. The semantic model is unchanged — only the emitted $Type differs — so
the tree is built with the legacy name and rewritten when targeting 11.9+.

- modelsdk (default engine): applyCallMicroflowStorageName walks the built element
  tree; useCallMicroflowActivityName() gates on pv.IsAtLeast(11,9). Codec
  TypeDefaults + list-marker registered under both $Type names. Wired into
  CreateWorkflow/UpdateWorkflow and the ALTER-workflow activity serializer.
- legacy engine: renameCallMicroflowTypeBSON rewrites the serialized BSON, gated the
  same way in serializeWorkflow and SerializeWorkflowActivity.
- read path already folds both gen types into the one semantic CallMicroflowTask.

Tests: rename walk (both directions) + encode-validity under the new name; version
gate against the vendored 11.6.6 fixture; legacy BSON-rewrite unit test. Repro
mdl-examples/bug-tests/263-workflow-callmicroflow-storage-name.mdl; symptom row
added to fix-issue.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…NGS LANGUAGE (FINDINGS #6)

`alter settings LANGUAGE DefaultLanguageCode = '<code>'` accepted any string. A code
not configured in the project (e.g. 'nl_NL' on an en_US-only project) was written,
reported success, and the *next* `mx check` died with an unhandled
NullReferenceException — never a model error, so the corruption was invisible until a
later command.

Validate the code against the project's configured languages
(ps.Language.Languages) before writing; reject with the available codes and a
Studio Pro hint. Skipped when the language list is unavailable (empty) to avoid
false rejections. Verified end to end on the vendored fixture: 'nl_NL' rejected,
'en_US' accepted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…NDINGS #2)

The lexer defined `V3: V '3';` (a "syntax version V3" marker) that no parser rule
or visitor ever consumed — a dead token. Its only effect was to tokenize any bare
`V3` in the input as the V3 token instead of IDENTIFIER, so `V3` was unusable as an
attribute name, property name, or page binding while `V1`, `V2`, `V4`, `V5` (never
tokens) all worked. Quoting (`"V3"`) was the only workaround.

Removing the unused token lets `V3` tokenize as a normal IDENTIFIER. Verified: an
attribute `V3 = 'c'` in a create activity and `Attribute: V3` in a page binding both
parse, alongside the quoted form; grammar regenerated (`make grammar`); visitor and
executor suites pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…41)

A workflow `call microflow ... with (Name = ...)` mapping accepted a qualified
(Mod.Flow.Param) or quoted ("Param") name via the QualifiedName grammar rule and
stored its raw text. The executor then re-qualifies with `mfQN + "." + name`, so:

  - qualified input  Mod.Flow.Param  → stored Mod.Flow.Mod.Flow.Param → null
    ParameterId → runtime fails to load the model
  - quoted input     "Param"         → stored Mod.Flow."Param"        → CE1613
  - bare input       Param           → stored Mod.Flow.Param          → correct

Only the bare form worked — the one place MDL's "always quote identifiers" habit is
actively wrong. Normalize the mapping name in the visitor to the bare, unquoted last
segment so all three spellings converge on the correct single-qualified stored form.

Verified end to end: bare, quoted, and fully-qualified inputs all store
`MyFirstModule.ACT_Do.Item`. Unit test covers the normalizer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…c sugar, ADD doc (FINDINGS #3/#5/#13/#14/#4)

Five surprising parser rejects / silent-wrong writes where quoting or a keyword
behaved inconsistently with the rest of MDL:

- #3 index name can't be quoted: indexDefinition accepted only IDENTIFIER for the
  name → `index "idx_x" (Col)` was a parse error. Accept QUOTED_IDENTIFIER too
  (the name is advisory and discarded, as before).
- #5 role-name quoting inconsistent: DESCRIBE USER ROLE required quotes, DROP USER
  ROLE rejected them. Both now accept bare and quoted names; visitors handle both.
- #13 `sort by` quoted attribute stored a nonsense reference: a quoted qualified
  attribute (`sort by "Mod"."Entity"."Code"`) kept the quotes and only failed on
  write ("attribute does not belong to entity"). buildSortColumnMicroflow now
  unquotes each segment (bare dotted form), matching the SORT() list-op path.
- #14 `DataSource: ASSOCIATION $currentObject/…` didn't parse (keyword + sugar were
  mutually exclusive). Added the combined grammar branch; the existing
  VARIABLE&&SLASH visitor branch already handles it correctly.
- #4 doc: MDL spec README showed `alter entity … add (Attr: type)`, which the
  parser rejects. Corrected to `add attribute Attr: type`.

Grammar regenerated (`make grammar`). Verified end to end: quoted index name,
quoted qualified sort stored as `M.Thing.Code`, describe/drop role in both forms,
and `ASSOCIATION $currentObject/…` all parse/store correctly. Tests added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…ll gaps (FINDINGS #42/#36/#23/#48/#47)

- #42 DESCRIBE WORKFLOW dropped the `with (...)` param mappings: the modelsdk read
  path built a CallMicroflowTask with only Microflow+Outcomes and never populated
  ParameterMappings, so describe→drop→exec silently lost the mapping (which nothing
  then reports). Added microflowParamMappingsFromGen and wired it into both the
  CallMicroflowTask and CallMicroflowActivity read cases; the legacy sdk/mpr parser
  already read them. Round-trip test added; verified `describe workflow` now emits
  `call microflow M.ACT_Do with (Item = '$workflowContext')`.
- #36 SEC005 lint suggested `ALTER PROJECT SECURITY STRICT MODE ON`, a statement the
  parser doesn't implement. Strict mode is Studio Pro-only — the suggestion now says
  so instead of naming an unrunnable command.
- #23 documented `create or modify association` (the idempotent form) in the
  domain-model skill: plain `create association` is not idempotent and its failure
  aborts the rest of the script.
- #48 documented `set task outcome $Task '<Outcome>'` (there is no `complete task`)
  and the other workflow task statements in write-workflows.
- #47 documented that System-module enumerations are read from the runtime, not the
  .mpr, so mxcli can't resolve them — constrain on an attribute instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…sociation (FINDINGS #51)

A `create`/`change` activity assigning a one-qualifier member `Module.Name` that is
not an existing association was written as an *attribute* reference. A one-qualifier
name can never be a valid attribute (attributes are bare or Module.Entity.Attribute),
so the result was an invalid AttributeIdentifier and the next `mx check` could not
LOAD the project at all (StorageLoadException), even though `mxcli exec` reported
success. This commonly followed a non-idempotent `create association` that had
failed earlier in the script, leaving the association absent while a later `create`
still referenced it — a green exec into an unloadable .mpr.

In resolveMemberChange, when the domain model is available and a one-dot member is
not found in the module's associations (or cross-associations), reject it with an
actionable error ("create the association first ...") instead of serializing an
Attribute. Associations created earlier in the same script are visible via
GetDomainModel, so this does not false-positive on same-script associations; two-dot
qualified attributes (Module.Entity.Attribute) are still allowed.

Verified end to end: nonexistent association → clear error; same-script association →
succeeds; qualified attribute → succeeds. Regression test + repro added; full
executor suite (incl. cross-module association tests) passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Mendix requires widget names to be unique per page and rejects duplicates with
CE0495 "Duplicate name" — but `mxcli check --references` passed a page with, e.g., a
container and a listview both named `ruTop`, and the failure only surfaced at
MxBuild.

Added checkDuplicateWidgetNames to the page context validator: it walks the widget
tree, counts names, and reports each name used more than once (once per name,
first-seen order). Runs at check time under --references, before the build.

Verified end to end: a page with two `ruTop` widgets now reports the CE0495-class
error; unique-named pages pass; full executor suite (no false positives on existing
test pages). Unit + parsed-page tests added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
#40)

A workflow "call microflow" activity that did not map a required parameter of its
target microflow passed both `mxcli check --references` and `mx check`, but Studio
Pro rejects it (CE6677) and the workflow fails at the activity — the mapped and
unmapped forms were indistinguishable to the checker.

Added a reference-phase validator (runs under --references, where the target
microflow is introspectable): for each workflow call-microflow, look up the target
microflow's parameters via ListMicroflows and report any parameter not present in
the activity's `with (...)` mappings. Microflows created in the same script are
skipped (not yet queryable); a target not in the project is left to the
missing-reference check.

Wired as a new CreateWorkflowStmt case in validateWithContext. Verified end to end:
an unmapped `Item` parameter is now reported; the mapped form passes; full executor
suite green. Repro mdl-examples/bug-tests/265-workflow-unmapped-microflow-param.mdl.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…ute values (FINDINGS #17)

An aggregate (sum/count/average/…) or unknown function used inside a `create` or
`change` attribute value fails the build with CE0117, but `mxcli check` only
inspected return/if/declare/set expressions — the same MDL044 check never reached
attribute-assignment values, so `RowTotal = formatDecimal(sum($cells), '0.00')`
passed check and failed MxBuild with a message that didn't even mention `sum`.

Wired checkExprFunctions (MDL044) into the CreateObjectStmt and ChangeObjectStmt
cases of the microflow body walk. Verified: `sum()` in a create attribute now
reports MDL044 with the "assign the aggregate to a variable first" hint; legit
functions (formatDecimal/trim) pass; full executor suite green. Test cases added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…ize context var (FINDINGS #39 regression)

The 11.9 storage-name fix (CallMicroflowTask→CallMicroflowActivity) let the runtime
load the model again, but the retest found the new class also structures outcomes and
the parameter-mapping expression differently — so a call-microflow with a parameter
or a non-void return now failed `mx check` with two errors that the tolerant old
class had passed:

- CE6686 "outcomes do not match the configured microflow": autoBindCallMicroflow
  injected a single VoidConditionOutcome regardless of return type. The 11.9+ class
  requires outcomes that match the microflow — a Boolean return needs true/false
  BooleanConditionOutcomes. defaultCallMicroflowOutcomes now generates them from the
  target microflow's ReturnType (Boolean → two branches, else → single default).
- CE0117 "Error(s) in expression": the context parameter is named "WorkflowContext"
  and 11.9+ expressions are case-sensitive, so a user-written `$workflowContext`
  (the form in every example) is an undefined variable. normalizeWorkflowContextExpr
  rewrites it to `$WorkflowContext`.

Verified against real mxbuild on BOTH classes:
- Mendix 11.12.1 (CallMicroflowActivity): `call microflow ACT with (Ctx='$workflowContext')`
  on a Boolean-returning microflow → 0 errors (was 2).
- Mendix 11.6.3 (CallMicroflowTask): same script → 0 errors (no regression).

Unit tests for both helpers; bug-test 263 updated to the param+Boolean-outcome case.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…tly dropping them

A `create page X { ...widgets... }` with no `Layout:` clause reported "Created page"
but produced an EMPTY page: buildPageV3 only builds a LayoutCall when Layout: is
present, and the widget tree is built into that LayoutCall's placeholder arguments —
so with no LayoutCall the widgets have nowhere to attach and were silently dropped.
Mendix then rejects the layout-less page at build with CE1613 ("layout
'dummyModule.dummyName' no longer exists" — its internal placeholder for a missing
layout). mxcli check passed; the widgets were simply gone — the same silent
data-loss pattern the TimeRegistration findings are about.

buildPageV3 now returns an actionable error when a page has body widgets (or
placeholder blocks) but no LayoutCall, distinguishing "no Layout: clause" from
"layout not found". Empty layout-less pages are unaffected (nothing to drop), and
snippets (buildSnippetV3) are layout-less by design and untouched.

Discovered while reproducing FINDINGS #49 (which no longer reproduces) against a
real Mendix 11.12.1 project + mx check. Unit tests (widgets→error, empty→ok);
repro mdl-examples/bug-tests/266; fixed the pre-existing no-layout page in the
ce0148 example. Symptom row added to fix-issue.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…9+ roundtrip)

The 11.9+ write path emits `Workflows$CallMicroflowActivity`, but the legacy
BSON parser only registered `Workflows$CallMicroflowTask`. On an 11.9+ project
a written call-microflow activity read back as an unknown type, so DESCRIBE
emitted the `-- [Workflows$CallMicroflowActivity] ...` fallback comment instead
of `call microflow ... with (...)`. Register the new $Type against the same
parser (it already reads Outcomes + ParameterMappings) so the activity
round-trips on both pre- and post-11.9 projects.

Fixes the integration failures TestRoundtripWorkflow_Comprehensive and
TestRoundtripWorkflow_CallMicroflowWithParams on the 11.9.0 CI runner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
… bindings (FINDINGS #55)

ALTER PAGE INSERT/REPLACE into a ListView bound `from association` produced a
widget whose Attribute binding pointed at the wrong entity — the outer data
view's entity instead of the association's destination — or no binding at all.
`mxcli check` passed; the Mendix build then failed with CE1613 "The selected
attribute 'Module.OuterEntity.Attr' no longer exists", and DESCRIBE masked it by
printing only the short attribute name.

Root cause: the page mutator read the enclosing entity from
DataSource.EntityRef.Entity, which is only populated for a DIRECT entity ref
(database source). An AssociationSource stores its destination on the last
DomainModels$EntityRefStep of an IndirectEntityRef, so the mutator saw no entity
for the list and left the context at the outer data view's entity; the inserted
bare attribute then resolved against that outer entity.

Fix: extractEntityFromDataSource now also reads the IndirectEntityRef's last
EntityRefStep.DestinationEntity (new lastStepDestinationEntity helper), so a
list bound `from association` reports its correct child entity to INSERT/REPLACE.

Verified on real mxbuild 11.12.1: the nested dataview→association-listview→insert
and →replace cases now `mx check` with 0 errors (CE1613 before). Unit guard
TestEnclosingEntity_AssociationSource; repro
mdl-examples/bug-tests/55-alter-page-insert-assoc-binding.mdl.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…ore workflow-action describe (FINDINGS #51–54)

Four findings from the TimeRegistration retest, all verified against real
mxbuild 11.12.1:

#53 — MDL048 rejected `retrieve … where [id = '[%CurrentUser%]']`, the standard
signed-in-user idiom (mx check → 0 errors). checkXPathIdConstraint now skips a
`'[%…%]'` server-token operand; a real stored-id value is still flagged.

#52 — MDL045 rejected division whose divisor is an association-attribute path,
e.g. `round($a div $obj/Attr * 100)`. The grammar parses div/*/`/` at one
precedence level, so `$a div $obj/Attr` mis-nests as `($a div $obj) / Attr` with
`Attr` a bare identifier; MDL045 saw the `/` as division. Mendix has no `/`
division operator and re-parses the raw `$obj/Attr` as a path — the serialized
output preserves the `/` and mx check passes. exprHasSlashDivision now ignores a
`/` whose right operand is a bare IdentifierExpr (member navigation).

#54 — `describe microflow` printed `-- Empty action` for `set task outcome`
(and open user task / notify workflow) under the default modelsdk engine, so a
describe→drop→exec round-trip silently dropped it. The write path and describe
formatter already handled these; only the modelsdk read case (actionFromGen) was
missing. Added SetTaskOutcome/OpenUserTask/NotifyWorkflow read cases.

#51 — `create association` erroring on re-run is correct SQL-shaped semantics
(not idempotent); the idempotent form `create or modify association` was
undiscoverable. Improved the "already exists" error to name it (and
`drop association …`).

(#50 daysBetween sign and #52's dateTime-literal restriction are genuine Mendix
platform behavior — the latter is already surfaced by MDL046 — so no code change.)

Tests: TestValidateMicroflow_XPathIdConstraint (CurrentUser token),
TestValidateMicroflow_SlashDivision (div-by-assoc), TestActionFromGen_WorkflowActions.
Repros: mdl-examples/bug-tests/{52-53-microflow-check-false-positives,54-describe-set-task-outcome,51-create-or-modify-association}.mdl.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
`bun install` floats typescript to ^6.0.2, which no longer auto-includes
every `@types/*` package on disk — `types` must be listed explicitly.
Without it tsc reported 43 errors on a clean checkout (`process`,
`console`, `setTimeout` unresolved, plus the implicit-any fallout on
node callbacks), so `make lint` failed before any change was made.

- tsconfig.json: declare `"types": ["node", "vscode"]`
- extension.ts: create the output channel with `{ log: true }` so it is a
  `LogOutputChannel`, which is what vscode-languageclient 10's
  `LanguageClientOptions.outputChannel` requires

CI only runs `make lint-go`, which is why this went unnoticed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012XR649rKk68z6gBpngu6MA
…ERT/REPLACE bindings (FINDINGS #55)

Retest of #55 showed the association + database datasource cases are fixed, but
a microflow/nanoflow datasource still bound nothing: inserting/replacing a
data-bound widget in a list bound `datasource: microflow …` produced an unbound
attribute (mx check CE0402 "No value specified" / CE1613).

Unlike a database (direct EntityRef) or association (IndirectEntityRef) source,
a MicroflowSource/NanoflowSource stores no entity in its own BSON — the entity
is the flow's RETURN type, which lives in the flow document. So the mutator's
BSON walk yielded "" and the bare inserted attribute resolved against nothing.

Fix: the page mutator's new EnclosingDataSourceFlow returns the qualified name
of the microflow/nanoflow governing the target's context — the nearest ENCLOSING
datasource for sibling INSERT/REPLACE, or the widget's OWN datasource for INSERT
INTO — via findNearestDataSourceDoc, which returns the nearest datasource *doc*
so a nearer non-flow source (database/association) correctly shadows an outer
flow. The executor (resolveDataSourceFlowEntity) then resolves that qualified
name to the flow's return entity via the existing
getMicroflowReturnEntityName / getNanoflowReturnEntityName, and uses it as the
widget's entity context when the BSON walk found none.

Verified on real mxbuild 11.12.1: INSERT and REPLACE into a microflow-sourced
ListView now mx check with 0 errors (CE0402 before). New EnclosingDataSourceFlow
interface method + mock/mcp impls (mcp is a no-op — its model resolves entities
directly). Unit guard TestEnclosingDataSourceFlow (incl. nearer-source shadowing);
repro extended in mdl-examples/bug-tests/55-alter-page-insert-assoc-binding.mdl.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…em (mendixlabs#801)

ALTER SETTINGS — any section, not just CONFIGURATION — serialized every
ServerConfiguration from the semantic model, which carries only the fields
mxcli understands. Everything else in the stored document was therefore
silently deleted on write:

- CustomSettings replaced with an empty array
- Tracing replaced with null
- OpenAdminPort / OpenHttpPort reset to false (the reader never populates
  them, so the model always held the zero value)
- the Configurations / ConstantValues / CustomSettings version markers
  downgraded from 3 to the hardcoded 2
- constant overrides rewritten with a flat "Value", the shape Studio Pro
  and mxbuild ignore — so after one ALTER SETTINGS every override looked
  empty in Studio Pro, and Integer/Long constants failed the build

The configurations are now overlaid onto the raw document they were read
from (ADR-0005 guard-don't-drop, which the surrounding settings parts
already followed): only fields the read path populates are written, each
list keeps its stored marker, and a constant override is updated in the
slot it already occupies so a nested SharedOrPrivateValue survives. A new
override — which has no stored shape to preserve — is written nested,
since that is what the platform reads. A configuration created by CREATE
CONFIGURATION takes its shape from a sibling, with the per-configuration
collections emptied and a fresh $ID.

The overlay lives in mdl/settingsoverlay because both write engines had
the same bug in duplicated form; sharing it keeps the codec engine
(mdl/backend/modelsdk) and the legacy engine (sdk/mpr) from drifting
again.

Also refuse the write outright when no raw parts were captured on read:
that path would have replaced every settings part with an empty array.

Known limitation: a project whose overrides were already flattened by an
earlier mxcli run keeps the flat shape, since the overlay preserves what
is stored rather than converting it. Those overrides need to be re-entered
in Studio Pro once.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012XR649rKk68z6gBpngu6MA
…endixlabs#805)

`alter settings configuration 'Default' HttpPortNumber = 'not-a-number'`
printed "Updated configuration 'Default'" and changed nothing. Every
Integer-typed setting parsed its value with the error discarded —
`if v, err := strconv.Atoi(valStr); err == nil` — so an unparseable value
skipped the assignment while the handler still reported success. DESCRIBE
SETTINGS then showed the original value.

The boolean settings had the same hole in a different form:
`AllowUserMultipleSessions = valStr == "true"` mapped every other spelling,
including a typo or a plausible 'yes', to false and reported success.

Both now parse through helpers that return a validation error naming the
setting and the offending value, so nothing is written. Covers all seven
sites: BcryptCost, AllowUserMultipleSessions, DefaultTaskParallelism,
WorkflowEngineParallelism, and HttpPortNumber / ServerPortNumber on both
ALTER SETTINGS CONFIGURATION and CREATE CONFIGURATION.

The same values are now reported at check time too (MDL-SET01 integers,
MDL-SET02 booleans), wired into `mxcli check` and the LSP so a typo
surfaces before the project is opened for writing.
TestTypedSettingsKeys_MatchExecutor guards the check-time table against
drifting from the executor's assignment switch.

Out of scope: range validation. A port of 0 or 999999, or a negative
BcryptCost, still parses as an integer and is accepted, as before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012XR649rKk68z6gBpngu6MA
…ct arg (write is correct)

Investigation of FINDINGS #56 ("describe page omits a show_page action's
arguments"), verified against mxbuild 11.12.1:

A widget button's show_page stores FormSettings.ParameterMappings as an empty
list [2] and Mendix infers the current-row object for each unmapped page
parameter. Storing an explicit `Argument: "$currentObject"` mapping makes
mxbuild report CE0115 "arguments do not match" — the original issue mendixlabs#296,
re-confirmed here. So the empty-mapping write is REQUIRED for a building app.

Consequence: `show_page X` and `show_page X($p = $currentObject)` serialize to
identical BSON, so describe→drop→exec re-produces a byte-identical valid page —
the round-trip is functionally lossless; only the redundant $currentObject
annotation is not echoed. No writer change (a fix there reintroduces CE0115);
clarified the serializer comment and added a bug-test documenting the verified
behavior and the boundary (a non-$currentObject widget page arg needs a
Studio-Pro WidgetValue reference to encode).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
fix: TimeRegistration findings #51#55 (ALTER-PAGE assoc binding, microflow check false-positives, workflow-action describe)
fix(settings): stop ALTER SETTINGS dropping configuration data and silently ignoring invalid values
@ako
ako merged commit c1fd4d7 into mendixlabs:main Jul 30, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants