Repository navigation
mxpatch: shared patch core + mxcli fix hashes verify around exec.sh --patch (#294, #295) - #374
Merged
Merged
Conversation
…#294, #295) project-bin/mxpatch.py finds a unit (byte pre-filter, then the codec) and an element by Name, gets/sets a value by key, and writes the unit together with its .mpr ContentsHash; dry run, already-applied and refuse-if-drifted harness. Both wf-*.py patchers write through it (master's left a stale hash, captured as a real mxcli v0.26.0 MISMATCH). mxunit_bson.py reads BSON 0x09. exec.sh --patch brackets the script with `mxcli fix hashes` (v0.25.0+) and restores on a new issue, before mxbuild. Fixtures replay captured mxcli v0.25.0/v0.26.0 output on a captured public model. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
- write_unit commits the new ContentsHash first, then swaps the file in via temp + os.replace; a failed swap puts the old hash back. The one window left (a kill between the two) is a MISMATCH fix hashes reports and a re-run repairs; documented in the code. - every file write goes through temp + os.replace; a failed temp write removes its .mxpatch-tmp. - no model path: usage + exit 2, no traceback. - exec.sh: the errored baseline's temp file is removed. - wf-set-call-captions refuses a caption that is not the stamped name (or --expect-caption); wf-add-path-terminators takes --expect-paths N. Fixture cases for each (kill/swap/tmp/commit faults, usage, drift). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
Merged
5 of 7 tasks
run-all.sh resolves a fixture's subject only from a `.sh`/`.md` path on its
usage line; test-mxpatch.sh declared a directory, so CI ran it with no
argument and it exited on `${1:?}` (FAIL rc=1 subj=<none>). The usage line
now names project-bin/exec.sh and the fixture tests that file's directory;
passing the directory still works. PASS 35/0 both ways.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
This was referenced Oct 15, 2026
Brings in #385, #388, #389. Clean merge: exec.sh (STOP-row scan, #349), skills/learned-mdl-preflight.md and CHANGELOG.md auto-merged with both sides kept; render-routing --check passes without a re-render. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
write_unit(backup=True) wrote <unit>.mxunit.bak beside the unit, where git shows it as untracked in the model folder and `git add mprcontents` commits it. Backups now go to $MXPATCH_BACKUP_DIR (exec.sh --patch points it into the snapshot it just took), else <tempdir>/mxpatch-bak/<model>/; a backup dir inside mprcontents/ is refused before anything is written. The fixture asserts no *.bak remains under mprcontents/ after a successful patch, the backup holds the old bytes, the default dir, and the refusal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
…by-rewrite (field run #390) - The BUILD-LOG row carries a hashes cell: baseline clean / N pre-existing, verify clean / left / N new / error, or NOT RUN with the reason. - A baseline MISMATCH the verify no longer reports is credited to the patch when that unit's file differs from the snapshot ("1 pre-existing repaired by this patch's rewrite"), and named in stdout; otherwise "other gone". - The baseline message says a unit the patch rewrites through mxpatch gets a fresh hash, and to repair the others with `fix hashes --repair`. - MXPATCH_BACKUP_DIR is exported into the snapshot just taken. - test-exec-hash-verify.sh: cases K, L, F2 and row assertions on A, B, C (38/38; 29/9 against 01b9b42's exec.sh, 12/26 against master's). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
Master moved again during the field-run fixes (#393, #394). Clean merge; render-routing --check re-run after it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
… review M1) Issues were matched by KIND+ID, so a unit that was a MISMATCH at baseline, rewritten by the patch and still stale afterwards, was logged "pre-existing, none new" and the patch kept -- #390's field state. A baseline MISMATCH still reported on a unit whose file differs from the snapshot now counts as new: restore, BUILD-LOG "verify 1 new", exit 1. Fixture case M (fails 5/5 on 4878984; 43/43 now). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
- L1: the inside-mprcontents/ refusal compares realpaths, lower-cased, so a symlink into mprcontents/ and MPRCONTENTS/x are refused too. - L2: backup names carry pid + a counter; two writes to one unit within a second keep both backups. - L3: the fallback is <tempdir>/mxpatch-bak-<uid>, created 0700 and checked to be ours and not a symlink, else a fresh mkdtemp. Fixture: symlink and case probes, same-second double write, 0700 mode (38 pass / 5 fail on 4878984; 43/43 now). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
MendixMau
marked this pull request as ready for review
October 15, 2026 10:26
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Direct lane
What changed and why (one paragraph):
This PR builds the core of #294 and the remaining
fix hashespart of #295.project-bin/mxpatch.pyis the shared layer that every targeted.mxunitpatch used to re-implement:$Type+Name(+ module, viaUnit.ContainerID), with a byte pre-filter before decoding.Nameand gets or sets a value by key. Sets keep the BSON type; string, bool, int32, int64 and double are supported, and anything else is refused.write_unit()writes the file and its.mprContentsHash(base64 SHA-256 of the file) together. The new hash is committed first, then the file is swapped in through temp +os.replace.find/get/set/verify/rehash) sits on top.Both shipped patchers (
wf-set-call-captions.py,wf-add-path-terminators.py) now write through it. On master they rewrote the unit and never touched the index, so every--applyleft a stale hash. Both now refuse drift, and both accept theexec.sh --patchform (<model.mpr> Module.Workflow).mxunit_bson.pynow reads and writes BSON 0x09 (UTC datetime).exec.sh --patchrunsmxcli fix hashes(read-only) twice: once before the script as a baseline, and once after a zero exit, before mxbuild. Any issue the baseline did not have restores the snapshot, writes a BUILD-LOG row and exits 1. Support is probed throughmxcli fix --helplistinghashes, the waydoctor.shprobes property-sets. On v0.24,fix hashes --helpexits 0 with the parent help (captured), so that call cannot be the probe. With no mxcli, or one older than v0.25.0, the step prints a loud NOT RUN and the patch continues to the gate. Blocking it would block the remedy (guard rule 7).Field evidence: no field project yet. Everything in the fixtures is captured, nothing hand-written (
tests/wave2/fixtures/mxpatch/CAPTURE.txt):testdata/testapp-views(a Studio Pro 11.14.0 app), with two additions:StartDateTimeis BSON 0x09.N mismatch(es), N missing file(s), N orphan file(s).means issues were found..mxunit.bakfile is not counted as an orphan.mxcli fix hashesreportsMISMATCH 056b39ea-… [Workflows$Workflow Approval]and exits 1.mxpatch set, it reports clean and exits 0.mxcli describereads the patched workflow and scheduled event back.testdata/expr-checkerit disagrees with the stored index on exactly the 67 units mxcli lists as MISMATCH (the same UUID set), and agrees on the other 302.testdata/pedappit agrees on 369 of 369.Field run
Issue #390, the #374 section: PASS on one real project (Mendix 11.15.0, 549 units), mxcli v0.26.0, macOS arm64, bash 3.2.
mxcli fix hasheson the project's committed HEAD found 1 MISMATCH, on exactly the workflow unit an old-style--patch(wf-add-outcome-persistent-ids.py, 2026-10-02) had rewritten without updating the index. That is field confirmation of the bug this PR fixes../bin/exec.sh --patch project-bin/wf-set-call-captions.py Module.Workflow --captions ... --expect-caption ... --apply: the baseline reported the pre-existing mismatch, the patch's write gave that unit a fresh hash (note: the index was already stale ... this write corrects it), the verify was clean, mxbuild reported 0 errors, exit 0.mxcli fix hashesafterwards found 0 mismatches, 0 missing, 0 orphan, anddescribe workflowread back the new caption.Field-run fixes (commits
0784677,a7adfe6):<unit>.mxunit.bakinsidemprcontents/, where git shows it as untracked andgit add mprcontentswould commit it.write_unitnow keeps the backup in$MXPATCH_BACKUP_DIR, whichexec.sh --patchpoints at<the snapshot it just took>/mxpatch-bakso it is pruned with the snapshot; standalone,<tempdir>/mxpatch-bak/<model>/. A backup dir insidemprcontents/is refused before anything is written.test-mxpatch.shasserts that no*.bakremains undermprcontents/after a successful patch.· hashes: baseline 1 mismatch (pre-existing), verify clean (1 pre-existing repaired by this patch's rewrite). Other forms:baseline clean, verify clean,... verify 1 mismatch left (pre-existing), none new,... verify N new(refused),(1 other pre-existing gone)when the unit was not rewritten, andhashes: NOT RUN (<reason>). "Repaired by this patch's rewrite" means that unit's file differs from the snapshot copy, and stdout names the unit.mxpatch.pygets a fresh hash with the write (the verify names it), and that the others are repaired withfix hashes --repair.Review fixes at
4878984(MERGE-AFTER-FIX; commits0d954f0,012242e):verify 1 new, and the exit is 1. Fixture case M fails 5 of 5 on4878984and passes now.mprcontents/refusal compares realpaths, lower-cased. A symlink intomprcontents/andMPRCONTENTS/xare both refused, and both are fixture cases.<tempdir>/mxpatch-bak-<uid>, created 0700 and checked to be ours and not a symlink, else a freshmkdtemp. The fixture asserts the 0700 mode.4878984:test-mxpatch.sh38 pass / 5 fail,test-exec-hash-verify.sh38 pass / 5 fail. Both are 43/43 now.Follow-up, noted and not built:
exec.sh --patch --expect-paths <unit...>, which declares the units a patch may rewrite. The verify could then refuse a write outside them, and the baseline message could name the affected units before the run instead of after it.fixtures/property-sets/already does.tests/andCHANGELOG.md, or 10 files and 1,518 lines with the two test scripts (987 / 1,431 at4878984, before the review fixes below). [field 2026-10-13] Promote a patch helper (unit/widget lookup, property set, write + hash, dry-run) and masked-media/aggregate-probe helpers #294 and [field 2026-10-13] Adopt mxcli v0.25/v0.26 commands: fix hashes, fix property-sets (Studio Pro merge safety), git note #295 are bundled on purpose, because the patchers' write path and the exec verify step share one hash contract. If you want it stacked instead: (1) codec + mxpatch + ported patchers, (2) the exec.sh post-step.test-mxpatch.sh43/43,test-exec-hash-verify.sh43/43.test-exec-approval43/43,test-exec-change-class21/21,test-exec-lock-watch84/84,test-bug07-0843/43.test-mxpatch.shgives 7 pass / 18 fail at the first head (no mxpatch, the 0x09 decode raises, and both patchers leave the index stale after--apply).test-exec-hash-verify.shgives 12 pass / 26 fail (the stale-hash patch is kept). Against the pre-field-run head01b9b42,test-exec-hash-verify.shgives 29 pass / 9 fail (no hash cell in the row, no fresh-hash or repaired lines, noMXPATCH_BACKUP_DIR), andtest-mxpatch.shfails on the.bakleft undermprcontents/..mprbesidemprcontents/starting from a unit path, and exec.sh passes$MPR, so theapp/layout should resolve the same way. The repaired-by-rewrite check reads the unit as$MODEL_DIR/<path>and<snapshot>/<path>, becausesnapshot-mpr.shcopiesmprcontents/to the snapshot's top level. Only the single-tree layout ran in the fixtures.os.path,os.replace, binary IO,tempfile), and the bash is 3.2-safe (check-portabilityis clean). The field run was on macOS with bash 3.2. Nothing was run on Windows.CHANGELOG.mdline, credit: jopterhorst ([field 2026-10-13] Promote a patch helper (unit/widget lookup, property set, write + hash, dry-run) and masked-media/aggregate-probe helpers #294, [field 2026-10-13] Adopt mxcli v0.25/v0.26 commands: fix hashes, fix property-sets (Studio Pro merge safety), git note #295)learned-mdl-preflight.md: the baseline goes from 75,028 to 75,069 words (budget 80,000).Review fixes (MERGE-AFTER-FIX, commit
5b4287c):write_unitcommits the new hash first, then swaps the file in through temp +os.replace, and a failed swap puts the old hash back. The one window left, a kill between the two, leaves the old unit intact and a MISMATCH thatfix hashesreports. A re-run repairs it; the fixture kills the process at that point, then re-runs and gets the captured hash. This is documented in the code..bakincluded, goes through temp +os.replace, and a failed temp write removes its.mxpatch-tmp.exec.shnow removes the baseline temp file when the baseline errors.wf-set-call-captions.pyreplaces a caption only while it is still the stamped microflow name, or the value given with--expect-caption Mod.MF='old'. Otherwise it exits 3 and writes nothing.wf-add-path-terminators.py --expect-paths Nrefuses a changed split.a5b0b34:test-mxpatch.sh27 pass / 8 fail,test-exec-hash-verify.sh25 pass / 1 fail (the temp-file leak).Still open in #294 (needs field data): copying a widget's shape from a known-good sibling with fresh IDs, pluggable-widget properties addressed by
PropertyKey, and the masking / aggregate-only test helpers. @jopterhorst, could you share the two helper libraries your project grew, and one captured page.mxunitthat has a pluggable widget inside a container (from any public or anonymised app)? With those, the widget-shape half can be built against real input instead of imagined input.Impact
exec.sh --patchgains amxcli fix hashesbaseline and post-verify. Any new hash issue restores the snapshot, writes a BUILD-LOG row and exits 1 (a new block, on the--patchpath only). With no mxcli, or one older than v0.25.0, it prints NOT RUN and does not block. Both workflow patchers now refuse drift (exit 3). Field-run additions: the BUILD-LOG row gains ahashes:cell, and unit backups move frommprcontents/to$MXPATCH_BACKUP_DIR(default: inside the exec snapshot). Neither adds a step or a block.test-exec-hash-verify.sh38/38,test-mxpatch.sh39/39project-bin/exec.sh,bin/lib/install-manifest.sh,skills/learned-mdl-preflight.md(baseline),skills/learned-workflow-patterns.md(on-demand, 2 lines),project-bin/mxunit_bson.py. Master is merged in at1512b09with clean merges, so #385, #388, #389, #393 and #394 are included. Platform: Python stdlib and bash 3.2. Field-run on macOS, not run on Windows. The verify needs mxcli ≥ v0.25.0. Reads the.mprContentsHash index andmprcontents/*.mxunit(producer: Studio Pro /mxcli exec),mxcli fix hashesoutput (producer: mxcli), andMXPATCH_BACKUP_DIR(producer:exec.sh --patch, the mandatory way to run a patch; the fallback is the temp dir). Writes model files (.mxunit+.mprContentsHash).git mergeonto1512b09, no conflictslearned-workflow-patterns.mdis on-demand, so it adds no always-loaded words.bin/render-routing.sh --checkon master1512b09and on this headlearned-workflow-patterns.mdedit (on-demand; the backup location) was not A/B'd separately: it is two factual lines in situational text.tests/behaviour/RUNBOOK.md), head pack vs baselineexec.sh --patchadds twomxcli fix hashesruns per patch. Field run (549 units, macOS arm64): the whole--patchexec took 103 s, mxbuild included; the two hash runs were not timed separately. The repaired-by-rewrite check is onecmpper baseline mismatch that has disappeared. The default (MDL) exec path and the gates gain no new step.elapsed: 103s)🤖 Generated with Claude Code
https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
Generated by Claude Code