alter microflow/nanoflow: graph splice, placement, insert/replace/drop (#736) - #739
Conversation
…lementIDs, Writer.UpdateRawUnitPatch) TransplantIDs pairs elements by type and position, which re-pairs the surviving flows of a patched microflow onto their neighbours' $IDs after a drop. A write that started from the stored bytes skips it; elision and the storage-GUID guard still apply. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e, replace, drop) Splices a fragment into the raw stored unit: appends only the fragment's objects and flows, rewires the flow around the target by its pointers and the rewired end, moves nodes past the insertion point to make room, and never rewrites an $ID. Save refuses a unit in which anything still points at a removed element or two elements share an $ID. Refuses what it cannot do safely: after a decision, before a join, inside a loop body, drop/replace of a decision or of an activity with an error handler, and a placement that would overlap an object. The modelsdk backend writes through UpdateRawUnitPatch (canon.Reconcile). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#736) alter microflow|nanoflow M.F { insert after|before <target> { … } replace <target> with { … } drop <target>; }. Targets are the #713 content addresses, resolved against the stored flow before any operation runs. Fragments are built with the create-microflow builder, seeded with the flow's variables, and cut out of their start and end events. A fragment variable that clashes with one the flow has, or one it reads that is not declared upstream of the insertion point, is an error; so is dropping an activity whose output is still read. Acceptance on PedApp VAL_Feedback: only the new log, the two flows around it and the shifted positions differ; an empty alter writes nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… handle
A handle has to parse as an alter target, and a target ends at the { of a
fragment.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Runs the shared splice on the stored flow and sends the difference as ped_update_document path operations in one update, after checking the live document still matches the .mpr (PED addresses entries by index). Drop and replace are refused: PED does not roll back a removal when an update fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Loop body flows are stored in the unit's Flows list, not in the loop, so removing a Studio Pro loop with two or more body activities left them pointing at removed objects and Save refused the unit (TestApp ACT_ConflictedWorkflowHelper_ApplyJumpTo). mx check 11.14 on the dropped and replaced copies gives the baseline error list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each operation was checked against the flow as stored only. Measured with mx check 11.14 on TestApp: two fragments declaring the same variable gave CE0111, and a fragment reading a variable a drop in the same statement removed gave CE0109, after "Altered microflow". The context now tracks what earlier operations declared, read and removed. Also count a fragment loop's iterator as the fragment's own, so loop fragments are no longer refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Independent review: pushed 534a595 (drop/replace of a Studio Pro loop with 2+ body activities was refused by the dangling guard because body flows live in the unit's Flows list; now removed with the loop) and 7abe76b (scope checks spanned only one operation: two fragments declaring the same var gave CE0111, and insert-reader + drop-producer in one statement gave CE0109, both after "Altered microflow"; loop fragments were refused over their own iterator). Tests written first, each fix revert-checked. mx check (11.13 PedApp / 11.14 TestApp copies) matches baseline for: insert before an error-handler path's first activity, drop in an error-handler path, inserts around a rejoin merge, insert before a nested decision, if/else and loop fragments, loop drop/replace. make lint, roundtrip integration, and package tests pass. 🤖 Generated with Claude Code |
Closes #736 (plan items 4.2b–4.2e of PROPOSAL_mdl_beta_syntax_freeze.md; ADR-0012 decision 3). Part of #714.
What
alter microflow|nanoflow M.F { insert after|before <target> { … } replace <target> with { … } drop <target>; }mdl/backend/mfmutator/splice.go) on the stored unit's BSON — neverUpdateMicroflow:$ID, case value, error-handler flag and other end are kept; a new flow carries on to the old destination on the side the old flow entered;replacere-points incoming flows at the fragment and the outgoing one from it (annotation lines move to the fragment);dropjoins incoming flows to the successor;$IDis ever rewritten.Saverefuses a unit in which any 16-byte binary still names a removed element, or two elements share an$ID(CLAUDE.md rule 1).$Var,'Caption', statement pattern,@n), all resolved against the stored flow before any operation runs. Fragments are built by thecreate microflowbuilder seeded with the flow's variables, then cut from their start/end events (several paths into the end get a merge).Writer.UpdateRawUnitPatch→canon.Reconcilewith a newcanon.ContentsOwnElementIDs(). Without it,TransplantIDsre-pairs the flows after a dropped one onto their neighbours'$IDs (measured on VAL_Feedback, see test plan). Elision and the storage-GUID guard still run.ped_update_documentpath ops in one update, after checking that the live document still matches the.mpr(PED addresses by index).drop/replaceover--mcpare refused: measured live, PED does not roll back removals when an update fails (a failed update left an activity and its flows removed).describe … with handles: the error-handler block's{is no longer part of a handle (it could not be written as a target).Test plan (what I ran)
make build;make lint(Go + TS) pass.go test ./mdl/visitor/ ./mdl/grammar/... ./mdl/backend/... ./modelsdk/canon/ ./modelsdk/mpr/ ./cmd/mxcli/... ./mdl/ast/ ./mdl/executor/pass.go test -tags integration ./mdl/roundtrip/pass (no allowlist change; this adds a statement, it does not change describe/exec of existing ones).VAL_Feedback(TestAlterMicroflow_PedApp_InsertAfterChangesOnlyTheSplice, 3-line script): every stored object keeps its$IDand list position; exactly one new object (a log activity); objects that changed differ only inRelativeMiddlePoint, all past the insertion point, only along x; exactly one new flow (log → 'Email is Valid?', entering on the old side); exactly one changed flow (out of$IsValidEmail), changed only inDestinationPointer,DestinationConnectionIndex,Line.DestinationControlVector; microflow and collection properties byte-identical; the new activity overlaps nothing; describe shows it in place.alter microflow … { };leaves the unit byte-identical (TestAlterMicroflow_PedApp_EmptyAlterChangesNothing;TestSplice_UntouchedUnitRoundTripsExactlyfor the decode/encode round trip).TransplantIDsbypass →TestReconcile_PatchOwnsElementIDsfails, andTestAlterMicroflow_PedApp_Dropfails with 8 flows showing neighbours' pointers under their$IDs;TestSplice_DropRefusesADanglingReferencefails;TestFlowPatchOps_StaleLiveDocumentIsRefusedfails;TestPrintedStatement_ErrorHandlerBlockOpenerIsNotPartOfTheStatementfailed before the change.mx check(scripts/mx-check.sh --version 11.13.0) on PedApp: baseline 1078 errors (theme/widgets stripped from the fixture); after the acceptance insert, after anif/elsefragment insert-before, and after replace + drop + insert in one statement: the identical sorted error list each time; nothing about the microflow.FeedbackModuleis a marketplace module and PED refuses writes to it ("Module 'FeedbackModule' is not writeable"), so the live check ran on Studio Pro-authored flows in writable modules:alter microflow Microflows.MicroflowReduce { insert after $CarList2 { log … } }andalter microflow Services.SaveOrder { insert after commit $Order on error { log … } }(an activity with a custom error handler) via--mcp:ped_check_errors→ "No errors found." for both; read back, the rewired flow kept its origin curve and the error-handler flow was untouched. A second alter on the now-stale.mprwas refused by the live-match check;dropover--mcprefused. I restored both microflows afterwards (unsaved in Studio Pro; SaveOrder's log activity and its two flows were recreated during an earlier failed two-phase attempt, so their in-memory element IDs differ from the saved file — same content). A probe microflowMyFirstModule.Zz736_SpliceProbeis left in the unsaved Studio Pro model (no delete tool without Concord).Design choices the ADRs did not settle
TransplantIDsvia a new explicit option rather than relying on it to be a no-op.SaveUnitrefuses to send them.mdl 1gating: this is new syntax, not a change of meaning (ADR-0011).Not in this PR (follow-ups)
return(guard inserts).drop/replaceover MCP (needs PED removal semantics pinned down).create or modify(GetPut for microflows) — separate issue per alter microflow/nanoflow: graph splice, placement and insert/replace/drop statements #736.🤖 Generated with Claude Code