Microflow/nanoflow create or modify as diff-then-patch: an unchanged definition writes nothing (#747) - #761
Merged
Merged
Conversation
Round-robin over the scripts that reach execution; every shard still upgrades and checks every script. TestShardsPartition proves the shards together execute each script exactly once. Locally 75+74+75 = 224, the unsharded count. Part of #757. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… nightly The single make test-integration step took ~22 of its 30 minutes, its wall time being mdl/roundtrip alone. Per-PR CI now runs executor, roundtrip, upgrade (3 shards) and other as parallel jobs, with an integration-passed aggregate. Nightly raises its step to 60 min and runs MXCLI_UPGRADE_ALL in 3 shards once. Closes #757. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ext.apply A pure refactor so create or modify can apply a derived patch through the same checks and splice an alter statement uses (#747). No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An existing flow is no longer rebuilt. The stored flow is described and parsed back, compared with the declared definition statement by statement (LCS over declaredMatches, recursing into if branches), and the minimal insert/replace/drop is applied through the #739 splice via the same alterFlowContext an alter statement uses. An unchanged definition writes nothing and reports Unchanged. A change the splice cannot express (header, loop or error-handler body, a moved node) is refused under mdl 1 and still rebuilt under mdl 0 with the MDL-V1-REBUILD warning (ADR-0011). Strikes all #721 class A microflow and nanoflow entries from the PedApp round-trip allowlist and the two nanoflow entries from studioProKnownLossy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
create or modify re-parsed the stored flow's description under the script's header. describe writes mdl 0, so under `mdl 1;` a stored line break described as `\n` read as a backslash and an n; a script stating exactly that then matched, reported Unchanged and wrote nothing, although the value it states differs from the stored one. The stored side is now described and parsed as mdl 0 always; the AST holds values, so it compares with a declared side parsed under any version. correctAmbiguousRanges applies in every case. Tests that exercised mdl 1 on VAL_Feedback relied on the misreading and now use a flow without a backslash escape for their mdl 1 leg; TestPedAppListActivitiesUnderMdl1 asserts the mdl 1 description of a flow of list activities is Unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… loop (#747) A declared loop that differed from the stored one only inside its body became a replace of the whole loop: every node in it rebuilt with new element IDs, and merges describe cannot show dropped - the rebuild's loss, confined to the loop, and under mdl 1 without the refusal the design and the skills promise for it. It is now a change the splice cannot make: refused under mdl 1, the warned rebuild under mdl 0. A changed loop iteration is still a replace. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner
Author
Independent review (#747)I pushed two fixes to the branch, each as its own commit. Each has a test that I watched fail before the fix went in.
Probed without finding a defect. Each case was run on a PedApp copy. I checked it with
Ran:
Remaining (not blocking):
🤖 Generated with Claude Code |
… the 700-line skill budget Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ako
added a commit
that referenced
this pull request
Sep 27, 2026
#761 locates stored activities by the @position describe prints; #748's canonical describe leaves derived positions out, so after an mdl 0 rebuild the differ could no longer address them (TestFlowModify_LoopBodyChange IsNotSpliced failed 20/20 once both were combined). describedFlowStmt now asks for the full layout via ctx.describeFullLayout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
11 tasks done
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.
Closes #747. Part of #714 (plan item 4.2g, ADR-0012 decision 3).
What
create or modify microflow|nanoflowon an existing flow is now diff-then-patch on top of the #739 splice engine, not a whole-document rebuild:@excluded,@applyentityaccess, expose, URL, export level, concurrency).declaredMatches. It compares whole statements, so signature and output variable both count. Each run of unmatched statements becomes an insert, replace or drop, aimed at the stored activity by the@positiondescribe printed for it. Anifthat differs only inside its branches is diffed branch by branch, because branch activities are top-level graph nodes.alterFlowContext.apply(extracted fromalter microflowin the first commit), so a derived patch gets the same fragment build, scope checks and splice as a hand-writtenalter.Unchanged microflow: …. A folder-only difference is a move.The fallback (the choice the issue asked to record)
The splice cannot express some changes:
For these:
mdl 1;it is refused with the reason, and nothing is written. The message points toalterfor activity changes, or to drop + create for a deliberate rebuild.Warning [MDL-V1-REBUILD]: …(alangver.Change, ADR-0011: a new refusal applies only under the header that opts into it).I did not keep the rebuild under mdl 1 anywhere. The round-trip harness shows it lossless on none of the 29 PedApp flows (all 29 were on the #721 class A allowlist).
Design choices the ADRs did not settle
@position/@curve/@anchor/@merge/@startmatches the stored one wherever it is drawn (R1: change only what differs). Geometry that is stated must equal the stored geometry. A statement that differs only in geometry is refused as "moved", not replaced, because the splice places only new nodes and a silent non-move would be a meaningless form (R11). Captions, colours and notes are compared exactly.@positionthey carry is ignored.describeafterwards shows where they actually went.@annotation(id: n1)included). Free annotations are compared at flow level and kept out of statement matching.removedIDs).describeprints a Custom rangelimit 1aslimit 1, which mdl 0 reads as the object range. Parsed as is, a headerlesslimit 1would look unchanged against a stored list.correctAmbiguousRangessets those parsed statements back to the list they stand for, keyed on output variable and@position. Describing under mdl 1 instead was tried and rejected:describedoes not emit mdl 1 string escapes yet, so VAL_Feedback's'…\n'strings re-parse differently.MDL-V1-REBUILDis gated in the executor, not the visitor. Whether a statement needs the fallback depends on what is stored, so it is not invisitor.LanguageChanges()or thefmt --upgraderegistry. A script upgraded tomdl 1;can meet the refusal at run time.Allowlists shrunk
mdl/roundtrip/allowlist_test.go: all 16 microflow and 13 nanoflow Round-trip harness: untracked describe → exec losses on the Studio Pro fixture (microflows, entities, pages, snippets, menus, roles, JS actions) #721 class A entries struck. 29 of 29 flows now keep GetPut and PutGet.mdl/executor/studiopro_roundtrip_test.go(studioProKnownLossy): the two nanoflow entries (ACT_Feedback_UploadImage,SUB_Feedback_GetOrCreate) now pass and are struck.Test plan
Run locally in the worktree:
make build,make lintgo test ./mdl/executor/ ./mdl/backend/... ./mdl/upgrade/ ./mdl/visitor/ ./cmd/mxcli/testrunner/ ./cmd/mxcli/theme/go test -tags integration ./mdl/roundtrip/(full package: PedApp harness plus new tests)go test -tags integration ./mdl/executor/(full package) and./mdl/backend/modelsdk/New tests, all on Studio Pro-authored PedApp flows (
mdl/roundtrip/flow_modify_test.go):UnchangedIsByteIdentical: VAL_Feedback describe → exec under mdl 0 and mdl 1. Unit bytes are identical, nothing is written, and the output says "Unchanged".InsertIsSpliced(the edited-flow control): one inserted statement is written. Every stored element keeps its$IDand type, all 10 merges survive (the rebuild left 5), and re-executing the new describe writes nothing.ReplaceKeepsSharedNote: a replace on PopulateUserAttributes keeps the one shared note on both activities. Only the replaced activity's subtree leaves.DropAndReplace: a drop plus a replace on SUB_Feedback_Sanitize under mdl 1.BranchEditIsSpliced: a message changed inside anifbranch of VAL_Feedback. Only that activity is replaced and the merges are kept.UnspliceableChange: a moved node is refused under mdl 1 with nothing written. Under mdl 0 it is rebuilt with theMDL-V1-REBUILDwarning.TestDeclaredMatches_OmittedGeometryIsNotADifference,TestLCSStatements_PairsTheUnchangedRuns.Revert checks. Each change was reverted or stubbed, and the listed tests failed with the expected symptom:
return false, nil): Unchanged (unit rewritten), Insert (merges 10 → 5,$IDs gone), Replace (parameter, end event and note$IDs gone) and Unspliceable (no refusal under mdl 1) all fail.correctAmbiguousRangesoff:TestPedAppRoundTrip_RetrieveRangefails (a headerlesslimit 1leaves the stored list).BranchEditIsSplicedis refused under mdl 1.UnspliceableChangegets no refusal.removedIDsskip off, or drops not ordered last:DropAndReplaceis refused with "$SanitizedPageName is still used by…".Studio Pro verification: MCP tunnel to TestApp, Mendix 11.14.0 (
--mcp http://localhost/mcp --mcp-dial host.docker.internal:7792):create or modifyofAdministration.ChangeMyPasswordandMicroflows.SplitMerge:Unchanged microflow: ….--mcp-traceshows no PED calls.logstatement before thereturnofMicroflows.SplitMerge(a merge-only, Studio Pro-drawn flow):Modified microflow: Microflows.SplitMerge (spliced: 1 inserted). It was sent as oneped_update_document, and the backend'sped_check_errorson the document came back clean. A rerun is refused by the live-document guard (7 objects live, 6 stored), which confirms the insert landed and nothing else changed count. The insert is still in the open, unsaved Studio Pro session.Follow-ups (not in this PR)
@positionon inserted statements, instead of ignoring it.describeunder mdl 1 should emit mdl 1 string escapes. Then the stored side could always be read under the latest version, andcorrectAmbiguousRangescould go.🤖 Generated with Claude Code