Skip to content

Sync ako/mxcli: Model SDK type floors, MPR v1 write deadlock and ContentsHash, action sources follow moves, exec file report - #1678

Merged
ako merged 26 commits into
mendixlabs:mainfrom
ako:main
Oct 30, 2026
Merged

ako merged 26 commits into
mendixlabs:mainfrom
ako:main

Conversation

@ako

@ako ako commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator

Syncs ako/mxcli:main into upstream: 12 commits from fork PR ako#1788. It is a batch of independent fixes, each in its own commit or short run of commits, with a failing test written first.

Fixes #1634.

Type floors: refuse a type the project's Mendix version doesn't have yet

  • What changes: the write path now refuses an element whose $Type the project's version doesn't have. It uses canon.UndeclaredTypeError, with 731 type floors extracted from the Model SDK.
  • Gates: workflow types that older Mendix lacks are gated at check time too.
  • Why it matters: on 9.24.30, wait for notification and get workflows used to produce a project that failed with TypeCacheUnknownTypeException. Now they are refused, and App.mpr stays byte-identical.
  • Verified:
    • The doctype scripts give identical results before and after on 9.24, 10.24 and 11.13.
    • 23 Studio Pro-made projects, from 9.1 to 11.16, hold no type the floor refuses.
    • Two existing tests wrote a type their own fixture's version lacks. Both were corrected without weakening them.

MPR v1 writes

  • A v1 write transaction no longer deadlocks. WriteTransaction on an MPR v1 project hung for good in WriteUnit, because the reader keeps one SQLite connection and the transaction already held it. The stored unit is now read through the transaction. The unit test hung on the old code and passes in 0.08s.
  • v1 writes keep Unit.ContentsHash in step. mxcli fix hashes now covers v1 too.

Other fixes

  • A Java or JavaScript action's source moves with it across modules. Before, a cross-module move left the source under the old module, so mxbuild built a stub that throws "not implemented". This was checked on 11.13 by inspecting the built class and bundle, because a green build alone proves nothing here: mxbuild builds on a copy.
  • exec names every file a statement writes, moves or removes beside the model (exec writes javasource/<module>/actions/<Name>.java for create java action but prints only "Created java action", so the file is easy to leave uncommitted #1634), such as the .java file of create java action, plus a summary line. A hidden --no-source-file-report flag turns this off.
  • An OData client's MetadataUrl is kept when another checkout re-runs the script. It used to be rewritten to that checkout's absolute path.
  • The legacy test runner renames every variable a test defines, so two tests no longer collide on a name.

Validation

  • make ci-local, make check-findings and make check-skill-index pass.
  • Each new test fails against the code without its fix.

Follow-ups (filed in the fork)

  • Generated storage-name aliases, such as Workflows$AnnotationActivity vs Workflows$Annotation.
  • A bare boundary event timer on 10.20–10.24 is unmeasured.
  • Lower instead of refuse, for timer kinds on 10.14–10.19 and lock/unlock on 9.21–9.24.
  • Check-time gates for the remaining workflow microflow actions

claude and others added 26 commits October 29, 2026 15:43
WriteTransaction.WriteUnit reconciles the new bytes with the stored ones
before writing, and read the stored ones through reader.GetRawUnitBytes,
i.e. through reader.db. The reader opens SQLite with SetMaxOpenConns(1),
and the caller's transaction already holds that one connection, so on
MPR v1 - where the stored bytes are in the database - the read waited
for it forever. codec.Store.SaveUnit, FlushUnits and modelsdk.Model.Flush
never returned on a v1 project. v2 reads mprcontents/ and never touched
the pool; mxcli exec writes through UpdateRawUnit and was not affected.

GetRawUnitBytes now delegates to rawUnitBytes(q, id), and WriteUnit
reconciles through reconcileAgainst with a reader that queries wt.tx.

Test: TestWriteTransactionWriteUnitV1DoesNotDeadlock opens a real v1
file through NewWriter (the real one-connection pool) and runs Begin /
WriteUnit / Commit under a 10s deadline; it times out on the old code
and passes in 0.08s. Measured on a fresh Studio Pro-authored 9.24.30
app: codec.Store.SaveUnit of a changed domain model did not return in
15s before, returned nil after, and mx check passes on the result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
…modules

`move java action A.X to B` reparented the document and left
javasource/a/actions/X.java behind (likewise a JavaScript action's .js
under javascriptsource/). Measured on 11.13.0: mx check passed and
`docker build` succeeded, but the class the app calls, jamb/actions/
JA_Hello, was mxbuild's generated stub throwing "Java action was not
implemented", while the author's code compiled only as an orphaned
jama.actions class; a JS action called from a page bundled "JavaScript
action was not implemented". DESCRIBE printed the placeholder body, so
re-executing its output replaced the code.

execMove now moves the source first (MoveJavaSourceFile /
MoveJavaScriptSourceFile), so an occupied destination refuses the
statement before the model changes, and moves it back if the rest of
the statement fails. A Java source's package line is rewritten by
javaactions.MoveSource, the one line mxbuild changes when run in place
on a moved source (testdata/mxbuild/JA_Moved*, user code mentioning
the old package kept). A move within the module leaves the source.

Tests: the golden pair; backend move, occupied destination, missing
source, JS; executor ordering, refusal before the model changes, undo,
same-module move. Each fails with its half stubbed out. Platform,
11.13.0 fresh app: after the fix the sources sit under jamb, DESCRIBE
shows both bodies, the built jamb class carries the user's code with
no orphan, and the page bundle carries the JS body; with the JS source
put back under jama, the bundle carries the throwing stub.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
On an MPR v1 project (unit contents inside the .mpr, Mendix below 10.18)
updateUnit, WriteTransaction.WriteUnit and insertUnit replaced a unit's
Contents and left ContentsHash describing the old bytes, or stored '' for
a new unit. mx check and MxBuild never read the column; Studio Pro does,
to detect what changed.

Studio Pro fills the column on every v1 unit with the v2 formula,
base64(SHA-256(Contents)), standard padded base64: 308 of 308 units of a
fresh 9.24.30 app, 20 of 20 in testdata/v1-project. The v1 branches now
store it in the same statement as the contents.

`mxcli fix hashes` covers v1: it compares each hash with the Contents in
its row, and --repair rewrites the ones an earlier mxcli left stale. The
per-exec drift warning stays v2-only, since on v1 it would read every
unit's contents on every command.

Measured on 9.24.30: create entity + create microflow left 2 stale rows
before (the DomainModel's old hash, the new microflow's ''), none after;
mx check 0 errors; fix hashes --repair repaired the stale copy.

Closes #1159

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
… the script

A relative MetadataUrl is stored as the absolute file:// URL of the
checkout that ran the script, and create or modify replaced it with its
own every run. The same unchanged script, run from another machine, a
moved folder or a git worktree, rewrote every OData client: 5 of the 20
writes of a full re-exec in the reported project.

The stored URL is now kept when it names the statement's file: equal
once normalized, or ending in the statement's project-relative path from
a directory that does not exist here (another machine, a moved
checkout) or that holds this project's .mpr (a worktree). A directory
that exists without it is a different file and the statement moves the
client off it. The contract is read from this checkout's file either
way. describe prints a stored URL inside the project as ./<path>, only
when that normalizes back to exactly the stored URL, so exec(describe)
stays unchanged and a generated script carries no machine path.

The absolute form stays. On 11.13.0 mx check reports 0 errors for a
relative value, a bare relative value and a dead absolute path alike, so
it never reads the value; mx has no headless contract refresh, and no
Studio Pro-authored client exists in any fixture. Nothing here shows what
Studio Pro's metadata editor makes of a relative URL.

Measured on 11.13.0: exec at P1, then from the app moved to P2 and from
a copy at P3: client unchanged both times (old binary: "Modified OData
client"); mx check 0 errors.

Closes #1499

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
TestV1WriteTransactionUpdatesContentsHash used a reader with an
unlimited connection pool to step around the v1 WriteTransaction
deadlock. That deadlock is fixed (the transaction reads the stored
bytes through itself), so the test now opens the fixture through
NewWriter, the one-connection pool every real write uses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
…unner

The after-startup runner (Docker, --local --legacy-runner) joins every
test into one microflow and renames each test's variables with a _N
suffix. Which variables to rename came from assignPattern, a regex that
matched `$x = call|create` only, so a variable defined by retrieve,
declare, a list operation or aggregate, send rest request, execute
database query or a loop iterator kept its name. Two tests defining it
declared it twice, and injection failed with MDL063/MDL052 (CE0111
"Duplicate variable name"). setup-annotation.test.mdl and the #1103
example both did.

The rename set now comes from the parsed test body through
executor.FlowDefinedVariables, the definition MDL063 itself counts,
walked with ast.StatementBodies so definitions nested in an if branch,
a loop or an error handler are renamed too. The runner's rename set and
the validator's refusal can no longer disagree. The parse wrapper is
shared with attachOnError (bodyParsePrefix).

Tests: retrieve / declare / list-operation / aggregate / REST / database
/ loop-iterator / nested cases plus upper-case CALL as the control, the
@throws path, and a sweep of every non-.fail test file in the repo
through GenerateTestRunner + ValidateProgram. With the regex restored,
15 shape cases and 3 repository files fail with MDL063/MDL052.

Fixes #1775

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
`create java action ... as $$ ... $$` stores the action in the model
and writes its body to javasource/<module>/actions/<Name>.java (a
JavaScript action's to javascriptsource/), but exec printed only
"Created java action: ...". A workflow that commits the model (.mpr
and mprcontents/) left the body untracked, and a clone got an action
with no logic.

The modelsdk backend now records each file or folder it changes beside
the model at the moment it touches disk (backend.SourceFileReporter):
java/javascript source written, moved by a rename, removed by a drop,
and the module source folders DROP MODULE deletes. The executor takes
those after every statement and prints one line per change, with a
project-relative path:

  Wrote javasource/probe/actions/JA_Hello.java (Java action body; commit it with the model)

and a program run that changed any ends with
"Files changed outside the model: N (javasource/, ...). Stage them
with the .mpr." A rewrite that leaves the content as it was does not
touch the file (WriteSourceIfChanged) and is not reported.

mxcli test injects its endpoint Java action and deletes the source at
cleanup, so its injection exec passes a hidden --no-source-file-report
rather than tell the user to commit that file.

exec has no JSON output mode, so there is nothing to add there. The
java-actions, javascript-actions and teamserver-git skills, the two
reference pages and `mxcli help exec` now say the body is outside the
model and must be committed with it.

Fixes #1618 (mendixlabs#1634)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
…c names

With exec now naming every file a statement writes beside the model
(#1618), the cross-module action-source move records itself
through noteSourceFile (Path and From) instead of printing its own
"Moved its source to ..." line, so exec reports it once, in the same
"Moved <from> to <to>" form as a rename.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
The generated version data holds property versions only, so nothing in
the write path knew when a $Type itself was introduced (#1774).
mendixmodelsdk_type_versions.json holds each storage type's introduction
(and the SDK's deletion) from mendixmodelsdk 4.117.0, extracted as text
by gen_type_versions.go; UndeclaredTypeError refuses a unit holding a
type the project's version does not have yet. Not wired in yet.

A storage name only a codec alias reaches gets no row: the first
extraction floored Forms$FormCallArgument at 9.7 through the alias of
PageParameterMapping, and Studio Pro 9.1.1 and 9.4.0 store it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
The property floor compared every key with the project's version and
never an element's own $Type, so a type newer than the project was
written and the project then did not load (TypeCacheUnknownTypeException).
It shipped four times (#1754, #1209, #1163, #1771), each fixed for its
one type (#1774).

normalizePropertySetsFor now runs canon.UndeclaredTypeError on the bytes
that land, after the lowering and the property floor, so a type with an
older shape is still lowered and a key a version lacks is still named as
the key. Every unit write passes it: insert, update and the transaction
path.

The user-task control asserted a 10.20 InterruptingTimerBoundaryEvent
writable on 10.14; 10.14-10.19 store a TimerBoundaryEvent. The control
now uses that type there, the 10.20 type on 10.20, and the 10.20 type on
10.14 is asserted refused.

Measured on 9.24.30: get workflows (Microflows$GetWorkflowsAction, 10.4)
made the project unloadable; it is now refused at write time and the
project is byte-identical. 23 Studio Pro-authored projects, 9.1.1 to
11.16.0, hold no type the floor refuses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
The write now refuses a type newer than the project; these name the
statement first, in check -p and exec's pre-flight, with MDL-VERSION01
(#1774). Each floor is the type's own introduction in
mendixmodelsdk 4.117.0:

  wait for notification                      10.0   WaitForNotificationActivity
  wait for timer                             10.6   WaitForTimerActivity
  boundary event timer (no kind)             10.14  TimerBoundaryEvent
  boundary event [non] interrupting timer    10.20  (Non)InterruptingTimerBoundaryEvent
  lock / unlock workflow naming a workflow   10.0   WorkflowDefinition*Selection

Boundary events are gated on every activity that holds them, and on the
event an `alter workflow … insert into … { boundary event … }` adds.

Measured on 9.24.30: a workflow with `wait for notification` made the
project unloadable (TypeCacheUnknownTypeException); now check -p and
exec refuse it and App.mpr is byte-identical. 10.24.24 and 11.13.0 write
it with 0 errors, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
The bug-test writes a workflow with both wait activities and a boundary
event inserted by `alter workflow`, and a microflow with `get workflows`,
`lock workflow` and `unlock workflow`. On 9.24.30, check -p reports
MDL-VERSION01 for the workflow, the alter and the microflow, and exec
writes nothing. On 10.24.24 and 11.13.0 it executes and checks with 0
errors, before and after the floor.

The finding records the measurements: 23 Studio Pro-authored projects
(the Marketplace's Workflow Commons ladder, samples and blank apps)
hold no type the floor refuses; the cached mxbuilds' class names agree
with the floors of the issue's types; the 9.24 doctype suite has the
same 61 passes and the same 17 first failures before and after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
Type floors, MPR v1 write fixes, action-source moves, and exec's file report
Symptom (#568, #646): `mxcli docker check` reported 0 errors
and exited 0, then `mxcli run --local` refused to start with CE0463
"The definition of this widget has changed" at the same widgets.
`docker check --no-update-widgets` reported them.

Cause: Check ran `mx update-widgets` on its temporary copy before
`mx check` by default, so it validated a widget-normalised model, never
the stored one. The CE0463s it cleared were real: MxBuild and
`run --local` build the project as stored. An earlier change only added
a closing note; the verdict and exit status stayed the copy's.

Fix: the default checks the project as stored, and the exit status is
the stored project's. Only when that check reports CE0463 does it run
update-widgets on the same copy and check again, then label each CE0463
"stale widget definition — `mxcli fix widgets` fixes it" or "persists
after update-widgets", print the fix command and the error count after
the resync. A project without CE0463 is checked once, as before. mx
check's text output does not name the page, so same-named widgets are
compared as counts. --update-widgets restores the old behaviour (check
only the normalised copy); --no-update-widgets is still accepted and
checks as stored without the re-check. The two are mutually exclusive.
The project is never modified, v1 or v2.

Tests that pinned update-widgets-before-check as the default now pass
UpdateWidgetsFirst (--update-widgets); the read-only and v2-preservation
guards cover all three modes.

Measured on Mendix 11.13.0, a blank app whose Data grid 2 3.4.0 package
gained one boolean property (11 stale instances):
- mx check on the stored project: 11 errors, all CE0463.
- old default: "The app contains: 0 errors.", exit 0.
- new default: 11 errors, all labelled stale, "After update-widgets:
  0 errors.", exit 1.
- --update-widgets: 0 errors, exit 0. --no-update-widgets: 11, exit 1.
- after `mxcli fix widgets` (11 units, MPR v2 kept): default 0 errors.
- the project tree's hash was unchanged by every docker check run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
…E0463

docker check's default changed (#568, #646): it checks the
project as stored and labels each CE0463 as a stale widget definition
(`mxcli fix widgets` fixes it) or one that persists after
update-widgets; --update-widgets is the old normalised-copy check.

Every page that described the old default is updated: the docker check
manual page (what it does, the labels, the three modes, a measured
example), the pluggable-widgets and marketplace guides, the validation
tutorial, docker build's note on its own pre-build check, `fix widgets
--help`, the synced validate-project command (a "Reading CE0463"
section) and the custom-widgets, create-page and marketplace skills.
diagnose-ce0463 now opens with which docker check you are reading and
says that pre-#646 "docker check passed" meant the resynced copy;
debug-bson and the widget-drift bug pattern say the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
…4.18

On Mendix 10.24 below 10.24.24 mxcli wrote documents without properties
Studio Pro writes (#1505, mendixlabs#1595). A nanoflow
re-created with drop + create lacked ExportLevel and ReturnVariableName,
and `mx diff` threw "Objects with ID … of type Microflows$Nanoflow do not
have the same properties"; a new microflow and entity lacked the
AggregateAction expression keys, DeleteAction.ErrorHandlingType,
WhileLoopCondition.Caption, DatabaseRetrieveSource.XpathConstraint and
EntityImpl Image/Source/ImageData.

Every one of those rows was in studiopro_property_defaults.json with
"since": "10.24.24" — the lowest version anyone had measured — and
propertyDefault.appliesTo reads since as the version that introduced the
key, so 10.24.0–10.24.23 got none of them.

Measured on 10.24.0.73019 and 10.24.18.103793, folded in with
internal/propertysets/cmd/propdefaults:
- the doctype gate with MXCLI_PROPERTY_SETS_RECORD on 10.24.0 (77 scripts);
- strip-and-convert on a blank app of each version plus probe content:
  remove every table row's key from every element, `mx convert -p`, record
  what Mendix writes back. Every key came back with the table's value; no
  value differs, no key is extra.

94 rows move to since 10.24.0 (86 from 10.24.24, AttributeRef.EntityRef
from 11.6.6, five from 11.6.8). Only since changes. Rows the measurements
showed absent on 10.24.0 (the 11.10/11.12 keys) and two not exercised
(Rest$OperationParameter/QueryParameter.TestValue), plus
DatabaseConnector$SimpleSqlDataType.DataTypeName (its connector module
needs 11.4), keep their floor.

TestCompletePropertySets_Issue1097Rows asserted EntityRef's old 11.6.6
floor; it now asserts the measured 10.24.0 one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
…sagree on

On Mendix 10.24.18 `mxcli fix property-sets --dry-run` printed "Every unit
already has the property names Studio Pro writes" for a nanoflow without
ExportLevel while `mx diff` threw on it (#1505). The command knew
only the keys the measured table fills on the project's version, so any
version or writer gap the table does not cover was invisible to it.

The project shows such a gap whatever the table covers: Studio Pro writes
the same property names on every element of a $Type (measured: every type
uniform on pedapp 11.13.0, expr-checker 11.6.6, testapp-views 11.14.0 and
blank 10.24.0 / 10.24.18 apps). mpr.PropertySetCensus counts, over the
units as the repair leaves them, which names each type's elements carry;
the command lists every name some elements of a type carry and others
lack as MIXED <type>.<key>, with up to three units on the smaller side,
and exits 1.

It reports and never fills (the issue's suggestion 2, reporting half): a
key no measurement covers on the project's version has no value Mendix
was seen writing there, and one taken from another version or element is
a guess that changes the model when it is wrong. The write-time
completion is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
…sults from the action

addListOperationAction typed its output by switching on the statement's
ast.ListOpX, addAggregateListAction its result by ast.AggregateX, and
addCallJavaActionAction its result by reading the statement's arguments.
A stored flow has the written action, not the statement, so nothing that
starts from a stored flow could type these the same way.

The typing now reads the action: listOperationResult (with
listResult.typeFrom and listOperationInput) over the microflows
ListOperation, aggregateResultType over the AggregateFunction and the
reduce return type, and javaActionResultType over the Java action's
definition and the variables the call passes. The full build calls them
with what it just built; behaviour is unchanged (a reduce's result is
named from the written return type, which reads the same for every type a
reduce can return).

Preparation for #1777.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
…e outputs for a fragment

Symptom (#1777, follow-up to #1769): a fragment spliced by
`alter microflow` (or `create or modify`) that names a member of a
stored list operation's output, a Java action's result or a retrieve
over a self-reference, unqualified, was refused with nothing written:
"cannot qualify member "Code": the entity its variable holds is not
known here". `create microflow` types all three.

Cause: alterFlowContext.storedVariables, the variables a fragment
starts from, left a ListOperationAction's and a JavaActionCallAction's
output "Unknown", and associationRetrieveType gave up on a
self-reference because which end it starts from was "not known".

Fix: storedVariables types them with the full build's own typing, now
reading the stored action (previous commit). A list operation's output
takes its input list's type (an element of it for head and find), a
Java action's result its declared return type, or, for a list of its
type parameter, the list the first list argument holds; both are
dependents typed in the fixed point, so the order the stored flow lists
them in does not matter. An aggregate gets the primitive the full build
declares. A name two origins give different derivations stays untyped,
as #1769's do; a head and a find of one list are one origin.

The stored retrieve records no direction (StartVariableName and the
association only), and Mendix follows a self-reference from its FROM
end: measured on 11.13.0, `retrieve $P from $Order/M.Order_Parent;
change $P (…)` builds, `count($P)` is CE0013 "Input variable 'P' must
be of type 'List'." So a self reference yields the object, a self
reference set the list (as before). Owner Both on a self-reference is
CE0002, so that case does not arise.

TestAlterMicroflow_FindOverUntypedListIsRefused used a filter's output
as its untyped list; that is typed now, so it uses the result of a Java
action returning a list of its type parameter bound by an entity
argument, which the full build does not type either. The assertions
are unchanged.

Measured on 11.13.0 (bug-test 1777): seven alters refused before, all
eight written after with every member qualified, mx check 0 errors,
describe -> exec "Unchanged".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
docker check checks the stored project; 10.24 property floors measured; alter fragments typed like the full build
`refresh catalog full source` describes every document, and each
microflow describe found its microflow, and every microflow it calls,
by listing every microflow of the project from disk. On a 4,753-unit
MPR v2 app one refresh opened 7,532,649 .mxunit files: 5.5 min on
macOS, 90+ min on Windows, where Defender scans every open.

- buildCatalog holds the units it reads in memory (CacheUnitReads),
  so each file is read from disk once per build.
- Reader.UnitByID / mprread.UnitByID read one unit by its ID (a
  unit's ID is its document's $ID on v1 and v2); GetMicroflow uses it.
- describeMicroflowMode reads the microflow the build pinned by ID.
- The void-call check reads called microflows by ID, falling back to
  the name lookup when two microflows share a name.

FactoryManagement: 7,532,649 -> 4,753 reads, 330 s -> 31 s, source
text byte-identical across all 3,718 rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82ttaGzay5CwiC2V7cf
fix(catalog): read each unit file once in a source catalog build (#1789)
…y need (#1791)

On MPR v2 nearly every command opened most unit files: on a 4,753-unit
app `show modules` 4,511, `describe microflow` 2,996, `describe page`
3,176. Names live only in each unit's BSON, so the container hierarchy
read every folder, the describe name map and by-name lookup read every
microflow, and `show modules` decoded seven document types to count
them. On Windows every open is scanned by Defender.

- The v2 unit index (ContentsHash -> entry) now holds Name and Excluded
  beside $Type; it moves to .mxcli/unit-index-2.idx so an older mxcli
  keeps its own unit-types.idx instead of the two rebuilding each other.
- Reader.ListUnitHeaders serves ListFolders and mprread.ListUnitHeaders
  from it; mprread.ListUnitsNamed finds a unit by name and reads it by ID.
- show modules counts from unit types (+ System built-ins from meta).
- flowReturnEntity and `show microflows in M` decode only what they use.
- populateViewEntityOql lists the modules once.

FactoryManagement, warm: show modules 4,511 -> 509 reads, describe
microflow 2,996 -> 93, describe page 3,176 -> 274, show microflows in M
3,072 -> 298. Output identical (3 projects, all 229 page describes,
full source catalog byte-identical).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82ttaGzay5CwiC2V7cf
fix(mpr): name units from the index so v2 commands open only what they need (#1791)
@ako
ako merged commit 93d481a into mendixlabs:main Oct 30, 2026
15 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

2 participants