Skip to content

docker build, TUI checker and eval runner no longer write to the project - #966

Merged
ako merged 12 commits into
mainfrom
fix/961-no-project-writes
Oct 3, 2026
Merged

ako merged 12 commits into
mainfrom
fix/961-no-project-writes

Conversation

@ako

@ako ako commented Oct 3, 2026

Copy link
Copy Markdown
Owner

Closes #961.

Before (measured)

Whole-tree sha256 + mtime diff before/after, on copies of testdata/testapp (11.14, real mx from ~/.mxcli/mxbuild/11.14.0). The v1 copy was made with mx update-widgets.

Path v1 v2
docker build TestApp.mpr rewritten (mtime; content too when widgets are stale). theme-cache/web/, deployment/ (~680 files), 160 javasource/ proxies, .classpath, .project added. TestApp.launch content changed. Same, plus every mprcontents/*.mxunit rewritten and restored with new mtimes.
TUI checker (mx check -j … -w -d) theme-cache/web/ and deployment/sass/ added. Model untouched. same
eval runner (mx check) same same

Fix

  1. docker build (and docker run / docker reload, which call Build): new buildOnCopy runs mx update-widgets, mx check and MxBuild on one temporary copy. It reuses fix: docker check never modifies the project; atomic catalog cache save (#951) #956's helper, renamed copyProjectToTemp. Only the PAD output directory is written (absolute path, default .docker/build). The output says so up front. MxBuild still gets the widget-normalised model, from the copy.
    • Output equivalence. On the testapp, v1 and v2, the new PAD has the same file list as the in-place build. It differs in 9 files. Control: two in-place builds of identical copies, old binary, differ in exactly the same 9 files (cache-bust stamps in index.html etc., operationIds, ModelRuntimeCompatibilityHash/model.mdp, and the absolute paths in native/metro.config.js/styles.js). So the outputs are equivalent up to MxBuild's own nondeterminism.
    • Rebuild time is unchanged (59s old, 58s new): a PAD build gains nothing from the project's deployment/.
    • Dropped: runUpdateWidgets (the v2-only snapshot) has no caller left, so it is removed with its 4 tests. snapshotStorageFormat stays for harvest.go.
    • Behaviour change: the project's deployment/ is no longer refreshed by docker build. Nothing reads it there: run --local runs its own deploy build. --help, docs-site/.../docker-build.md and mxcli fix widgets --help (which still described the old snapshot) are updated.
  2. TUI checker and 3. eval runner: new exported docker.MxCheckOnCopy(mxPath, mpr, args, stdout, stderr) runs mx check on a copy and rewrites output paths. Both callers use it. They also gain PrepareMxCommand (FreeType LD_PRELOAD), which they lacked.

Other mx/mxbuild invocations against a project (classified)

Code path Writes Verdict
docker check (check.go) copy since #956 fixed earlier
docker build / run / reload, testrunner's docker build --skip-check copy now fixed here
TUI checker, eval mx_check copy now fixed here
mxcli fix widgets / fix design-properties (RunToolPreservingFormat) model, harvested to keep v2 expected (the point of the command)
marketplace install (mx module-import, cmd_marketplace_install.go) model expected (installing a module). Its v2 handling was not re-audited here
marketplace/scratch.go (create-project, module-import) a scratch project not the user's project
mxcli new (create-project, SettleGeneratedSources = mxbuild --target=deploy) the new project expected
SyncJavaDependencies (mx sync-java-dependencies) vendorlib/ expected (the point of it)
run --local (mxbuild --serve, deploy target) deployment/, javasource/ proxies expected build output of a local run

Test plan

  • TestBuildOnCopy_DoesNotModifyProject (v1, v2 × default / --no-update-widgets / --skip-check / --dry-run). Stub update-widgets, check and MxBuild write into their target the way the real tools do. The test asserts:
    • the tree outside .docker/ is byte- and mtime-identical
    • no tool was pointed at the project
    • the PAD lands in the output dir
    • MxBuild saw the normalised model exactly when update-widgets ran
    • output names the project path, not the copy
    • the copy is cleaned up
  • TestBuildOnCopy_CheckFailureStopsBeforeMxBuild
  • TestRunMxCheck_DoesNotModifyProject (tui; stub mx script on PATH, v1/v2, JSON result still parsed)
  • TestCheckMxCheck_DoesNotModifyProject (evalrunner; v1/v2 × pass/fail, detail names the project path)
  • Integration, real mx: TestMxCheckOnCopy_LeavesProjectUntouched (11.14, v1+v2). TestBuild_LeavesProjectUntouched is a full MxBuild, v1+v2. It ran with 11.13, because 11.14's blank app needs JDK 25, which this box lacks, so it skips there. Existing TestBuild_PreservesMPRv2StorageFormat and TestCheck_LeavesProjectUntouched pass.
  • Revert checks:
    • build, unit: pointing buildOnCopy back at the project fails with changed App.mpr, theme-cache, .launch, added deployment/…, javasource/…/proxies, .classpath, and removed mprcontents/… on v2.
    • build, integration: restoring main's build.go + update_widgets.go fails TestBuild_LeavesProjectUntouched on v1 and v2: App.mpr changed, 370 mprcontents changed, 992 deployment added, 216 javasource added, .launch/.classpath/.project added.
    • tui / evalrunner: restoring main's checker.go / checks.go fails both tests with theme-cache/web/theme.compiled.css and deployment/sass/main.scss added.
  • Manual, after the fix: mxcli docker build on the v1 and v2 testapp copies. The only change in the project is .docker/ (4647 files added). Both exit 0.
  • go test ./cmd/mxcli/docker/ ./cmd/mxcli/tui/ ./cmd/mxcli/evalrunner/ ./cmd/mxcli/, make build, make lint, make check-conformance, make check-findings

Finding appended to cmd-mxcli.jsonl. docs-wiki/bug-patterns/package-operations-damage.md gets a paragraph on the recurring class (#763 → #808 → #951 → #961). CHANGELOG entry under Unreleased/Fixed.

Follow-ups

  • marketplace install's bare mx module-import on the project was not re-audited for the v2→v1 collapse here.
  • The PAD's native/metro.config.js now carries the temporary copy's path instead of the project's. Neither path exists inside the container, and the web runtime does not use these files.

🤖 Generated with Claude Code

ako and others added 12 commits October 3, 2026 17:42
…structure and the catalog (#963)

list workflows and show structure recursed over outcome flows only and
skipped boundary-event flows and event sub-processes, so TestApp Workflow1
listed 5 activities where the catalog counted 8. The catalog's walk moves to
wfnames.WalkActivities/CountActivities and all three use it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uct (#963)

The catalog has carried TotalActivityCount (loop bodies included) since #940;
microflows() now hands it to rules for microflows, nanoflows and rules alike.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…g create/change (#963)

commit $Order on a loop variable wrote no refs row because refs had no commit
kind. A commit action, or a create/change with commit Yes/YesWithoutEvents,
now emits FLOW -> ENTITY 'commit', resolved through the same intra-flow
variable map as change/delete. It stays out of the analysis graph and the
caller kinds. The ref_kind skill test now reads every RefKind constant, so a
new kind cannot ship undocumented. Catalog schema version 19.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tion

widgets_data gains ParentWidgetId (nearest indexed ancestor, so skipped
wrappers, layout grid rows/columns, tab pages and pluggable property /
object-list items are transparent), Depth (0 at the page or snippet root;
a list view template is a level), Class, Style, DynamicClasses, ActionType
(raw $Type of Action, else OnClickAction, else ClickAction) and
HasConfirmation (ConfirmationInfo on a microflow/nanoflow/workflow call).
Catalog schema 19. Tested on hand-built shapes and on Studio Pro-authored
TestApp pages. mendixlabs#1268

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…gets()

widgets() structs gain parent_widget_id, depth, class_name, style,
dynamic_classes, action_type, has_confirmation and page_ref.
mendixlabs#1268

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The widget table moves to write-lint-rules/catalog-tables.md (SKILL.md was
over the 700-line bound) with an example rule for inline styles, a class
allow-list and direct delete buttons. The vocabulary test scopes action_type
per section (activity vs widget), holds documented widget action types to
codec-registered storage names, and pins that only flow calls carry a
ConfirmationInfo. mendixlabs#1268

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ction' into c24-965

# Conflicts:
#	CHANGELOG.md
#	mdl/catalog/tables.go
#963's commit refs and mendixlabs#1268's widget columns both bumped 18 -> 19 on parallel
branches. A cache built at 19 by either alone would never rebuild for the
other, the 15/16 collision again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The TUI checker (after every change) and the eval runner's mx_check ran a
plain `mx check <project>`, which writes theme-cache/web/ and
deployment/sass/ into the project. Measured with mx 11.14 on a v1 and a v2
copy of the testapp: the model is left alone, those two folders are added.

New docker.MxCheckOnCopy runs `mx check` on copyProjectToTemp's copy (the
#956 helper, renamed now that it is not check-only) with output paths
rewritten to the project's, and applies PrepareMxCommand, which these two
callers lacked. mxCheckCmd takes extra args for the TUI's -j/-w/-d.

Part of #961.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tory

`docker build` (and docker run / reload, which call it) ran update-widgets
on the project under a snapshot that restored only MPRv2 storage, then
mx check and MxBuild on the project itself. Measured on the 11.14 testapp:
an MPRv1 .mpr was rewritten, every MPRv2 .mxunit was rewritten and put
back with new mtimes, and theme-cache/, deployment/, 160 javasource/
proxies, the .launch file, .classpath and .project were written into it.

buildOnCopy now runs all three tools on one copyProjectToTemp copy and
writes only the PAD output directory (absolute, default .docker/build).
MxBuild still sees the widget-normalised model, from the copy. The PAD
differs from an in-place build in the same 9 files in which two in-place
builds of identical copies differ (cache-bust stamps, operation ids,
native metro paths), and the rebuild time is unchanged (58s vs 59s).

runUpdateWidgets (the v2 snapshot) has no caller left and is removed with
its tests; build_readonly_test.go covers v1 and v2 with stub tools, and
TestBuild_LeavesProjectUntouched with real mx and MxBuild.

Part of #961.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ako
ako merged commit cd5e380 into main Oct 3, 2026
31 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.

docker build, the TUI checker and the eval runner still write to the project

1 participant