Skip to content

fix(mcp): ALTER MICROFLOW insert lands on Studio Pro 11.15 - #1545

Open
ako wants to merge 3 commits into
mainfrom
fix/mcp-splice-addresses-new-elements
Open

ako wants to merge 3 commits into
mainfrom
fix/mcp-splice-addresses-new-elements

Conversation

@ako

@ako ako commented Oct 21, 2026

Copy link
Copy Markdown
Owner

Stacked on #1534 (fix/mcp-create-flow-sides-and-notes), which is itself stacked on #1528. This PR targets main, so until those two merge its diff also shows their commits. Merge them first: this PR's own change is the last commit.

Problem

On Studio Pro 11.15.0-rc.4, every alter microflow … { insert after … begin … end; } over --mcp failed:

ped_update_document … undefined is not an object (evaluating 'u.$Type')

It happened with or without a note; a plain one-activity insert reproduced it. Removing the failing set exposed two more problems that the crash had been hiding:

  • the new activity read back with "No action defined";
  • an inserted IF/ELSE was wired to the wrong objects (the activity before it led into the else-branch log).

Cause (measured by direct ped_update_document probes)

The tool's own rule is that "every index you provide refers to the array as it exists BEFORE this call". The splice sent the whole insert as one update and addressed the elements it added in that same update by index.

Same update as the add Result
set /flows/N/line/originControlVector fails the whole update (u.$Type)
set /flows/N/line (whole curve) accepted, not applied
set /flows/N/isErrorHandler accepted, not applied: the flow reads back as an ordinary one
set /objectCollection/objects/N/action or …/relativeMiddlePoint accepted, not applied
put line / isErrorHandler / action in the constructor value accepted, ignored
add the flow in its element shape refused (NodeUnableToConvertElement, String, System.Guid)

Every one of these sets lands in a later update. $id() references do resolve to elements added earlier in the same update.

Separately, several adds to one array without an index are stored in reverse, and $id() resolves against that final order. Three objects came back as 3, 2, 1. A one-activity insert hides this, because it adds only one object and one flow. Adds given one shared index (the stored length) keep their listed order. That is PED's documented rule for adding several elements at a position. It also explains #1534's "an AnnotationFlow added before a SequenceFlow is stored after it".

Fix

flowPatchOps now returns two operation lists, and SaveUnit sends them as two updates:

  1. Structure, all or nothing: the new elements (constructor properties only, each at the stored length as its index, wired with $id()), rewired flow ends, the stored elements moved to make room, and control vectors on flows that already existed.
  2. Fill, on the elements update 1 created: actions, split conditions, loop sources, isErrorHandler, and the new flows' control vectors (0;0 is the schema default and is not sent).

The atomicity trade-off

The splice relied on a single update being all or nothing. That cannot be kept on 11.15: nothing in the update that adds an activity can give it its action. If update 2 fails, update 1 is not rolled back, and the insert is left in Studio Pro without its fill. This is the same contract as CreateMicroflow, which creates and then sets activities. The error says so (the insert into X was added in Studio Pro, but setting up its new activities and flows failed …), and ped_check_errors names what is missing. A retry cannot insert twice: the live/local index check refuses it.

Considered and rejected: sending only the cosmetic control vectors second and refusing everything else. That fixes the crash but leaves every insert with "No action defined".

Verification

  • Unit tests (mdl/backend/mcp/microflow_mutator_test.go):

    • TestFlowPatchOps_NewElementsAreFilledInOnceTheyExist: the first update addresses nothing it adds; the second sets the action and the new flow's vectors, and nothing else.
    • TestFlowPatchOps_AddsKeepTheirListedOrder: every add carries the stored length as its index.
    • TestFlowPatchOps_NewErrorHandlerFlowIsMarkedOnceItExists: isErrorHandler is set in the fill, not with the add.
    • TestNewFlowLineOps_LeavesOutDefaults: 0;0 vectors are not sent.

    Control: against the original save path, all four behavioural tests fail with the reported shape (one update, /flows/3/line/… set beside the add), and the ordering test fails with index <nil>.

  • Live, Studio Pro 11.15.0-rc.4, through the MCP tunnel. Each base document was created once over --mcp and once locally, then the same ALTER ran both ways and every flow was compared after resolving PED's $id(...) references to positions:

    • plain insert: 4/4 flows match (ends, sides, control vectors) and 5/5 object positions;
    • IF/ELSE insert: 9/9 flows and 9/9 positions, including the true/false case values, the action and the condition;
    • note insert (two activities and a note): 6/6 flows, the note line 2 → 0, the note's position, size and caption. This is the end-to-end check fix(mcp): a microflow created over MCP keeps its flow sides and notes #1534 could not run.
    • ped_check_errors was clean on two successive calls for all three.
  • Live control: the unfixed binary reproduced the u.$Type failure on the same plain insert, and left the live document unchanged (4 objects, 3 flows).

  • make build, make test, make lint and make check-mdl pass.

Repro script: mdl-examples/bug-tests/mcp-alter-insert-addresses-new-elements.mdl. Measurements are in docs/03-development/PED_MCP_CAPABILITIES.md (11.15 section, "what one update can address"); the finding is recorded under .claude/skills/fix-issue/findings/mdl-backend/.

Not fixed here

  • Other multi-add updates in the MCP backend may hit the same reversal on 11.15. Candidates are loops that append to one array in one update: domainmodel.go (entities/attributes), navigation.go (menu items), workflow.go. Not audited.
  • Custom error handlers are still refused over MCP (microflow.go), on both create and ALTER. The refusal's premise ("PED has no error-handler flow") no longer holds on 11.15: isErrorHandler is settable in a later update. The splice already marks such a flow in its fill, but MDL cannot reach that path until the refusal is lifted and verified.
  • The create path never sends control vectors, so every flow of an MCP-created microflow keeps the 0;0 default. The local builder writes 0;0 for most flows as well, which is why they compare equal here.

Test documents left in the open Studio Pro project (PED has no delete), all in MyFirstModule:

  • complete, verified: MCPA_XPlain, MCPA_XIf, MCPA_XNote, MCPA_WPlain;
  • probes: MCPA_VecProbe1, MCPA_OrderProbe;
  • left broken by intermediate builds during this work: MCPA_SplicePlain and MCPA_VPlain (new activity without an action), and MCPA_WIf (mis-wired, unfilled);
  • never altered: MCPA_VIf, MCPA_VErr, MCPA_WErr, MCPA_XErr.

🤖 Generated with Claude Code

ako and others added 3 commits October 21, 2026 09:07
The create path sent no connection sides, so Studio Pro drew every flow
right -> left: @anchor, CASE outcome anchors and the builder's own sides
were lost (11.15.0-rc.4: a CASE read back 1 -> 3 on all eight flows). A
note failed the whole create, since mapObjectTree had no Annotation case.

buildFlowDocContent now maps each sequence flow's indexes onto
originConnectionSide/destinationConnectionSide (setPEDSides, shared with
the splice). It also writes notes as Microflows$Annotation and their
lines as Microflows$AnnotationFlow elements: origin/destination plus the
required connection indexes, defaulting through the new
AnnotationFlow.SidesOrDefault, which the modelsdk writer uses as well.
The skeleton adapter keeps a note line's $Type and shifts its ends past
the parameters. On the pre-11.14 constructor, whose flows are untyped,
notes are refused rather than risk a note line read as a sequence flow.

The splice sent a note line with the constructor's originId/destinationId
keys, which PED refuses. It now uses the same mapping and adds note lines
after its new sequence flows, because PED stores an AnnotationFlow after
a SequenceFlow added later in the same update.

Verified live against Studio Pro 11.15.0-rc.4: the same script gives
identical sides on all 10 flows over --mcp and on the modelsdk engine,
note lines included; with the fix reverted every flow read back 1 -> 3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every insert over --mcp failed on 11.15.0-rc.4 with `undefined is not an
object (evaluating 'u.$Type')`. The splice sent the whole insert as one
ped_update_document and addressed the elements it added in that same update
by index. PED resolves a path against the document as it was before the
update, so none of those sets reach a new element: a set below a new flow's
line fails the whole update, and any other set (an activity's action, a
flow's isErrorHandler) is accepted and lost. Separately, several index-less
adds to one array are stored in reverse and $id() resolves against that
final order, so an insert of more than one element was wired to the wrong
objects.

The insert is now two updates. The first adds the new elements (constructor
properties only, each at the stored length as its index, wired with $id())
and moves the stored ones. The second fills in the new elements: actions,
conditions, loop sources, isErrorHandler and control vectors (0;0 is the
schema default and is not sent). A failed second update leaves the insert
incomplete in Studio Pro, the same contract as CreateMicroflow's
create-then-set; the error says so.

Verified live on 11.15.0-rc.4: plain, IF/ELSE and note inserts match the
default engine on every flow's ends, sides and control vectors and every
object's position, and ped_check_errors is clean on two successive calls.

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

This branch has not been deployed

No deployments
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.

1 participant