Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/backend", "date": "2026-10-21", "symptom": "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')`, with or without a note; a plain one-activity insert reproduced it. With that set removed, the insert landed but ped_check_errors reported `No action defined` on the new activity, and an inserted IF/ELSE came back wired to the wrong objects (the activity before it led into the else-branch log).", "cause": "flowPatchOps sent the whole insert as one update and addressed the elements it added in that same update by index: /flows/N/line/<end>ControlVector, /objectCollection/objects/N/action, …/relativeMiddlePoint, /flows/N/isErrorHandler. PED resolves a path against the document as it was before the update, so none of those reach the new element. A set below a new flow's line dereferences a line that is not there yet and fails the whole update. Any other set is accepted and lost, and so is the same property put in the constructor value (an ActionActivity's constructor takes only x/y/caption). Separately, several index-less appends to one array in one update are stored in reverse, and $id(/…/N) resolves against that final order, so with more than one new object every flow reference pointed at the wrong one.", "file": "mdl/backend/mcp/microflow_mutator.go (SaveUnit, flowPatchOps, newFlowLineOps)", "insight": "Two direct ped_update_document probes settle what an update can address, and they are cheaper than any reading of the code. First, add an element and set one of its properties in the same update, then read it back. Second, send the same set in a separate update. The difference is the whole bug. The separate-update set always landed: control vectors, action and isErrorHandler alike. So the fix is two updates. The first is all or nothing: the new elements, wired with $id(), and the stored elements moved to make room. The second fills in the new elements. A failed second update leaves an incomplete insert in Studio Pro, the same contract as CreateMicroflow's create-then-set. That is the price of 11.15, and the error says so. For ordering, give every add the array's stored length as its index; PED keeps adds at one shared index in listed order (its documented rule for inserting several elements at a position). The 1534 finding's 'an AnnotationFlow added before a SequenceFlow is stored after it' was this reversal, not something about annotation flows. A one-activity insert hides both bugs, because it adds one object and one flow, so verify with an insert of several elements (an IF/ELSE) and compare every flow's ends, sides and vectors against the same script run locally.", "refs": ["ako/mxcli#1534"]}
73 changes: 64 additions & 9 deletions docs/03-development/PED_MCP_CAPABILITIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -597,15 +597,70 @@ servers.
- The pre-11.14 (full) constructor's flows carry no `$Type`, so a note's line could
only be read as a `SequenceFlow`. The backend refuses notes there rather than
risk an execution edge out of a note.
- Within one `ped_update_document`, an `AnnotationFlow` added *before* a
`SequenceFlow` is stored *after* it. So the splice adds its new sequence flows
(and the sets that address them by index) first, and note lines last.
- **Open: ALTER MICROFLOW insert over MCP fails on 11.15.** Setting
`/flows/N/line/originControlVector` on a flow added in the same update fails the
whole update with `undefined is not an object (evaluating 'u.$Type')`. This
happened without any note: a plain one-activity insert failed, and so did a lone
`SequenceFlow` add followed by that set. Setting the flow's whole `line` instead
is accepted but **not applied**: the read-back has no control vectors.
- ~~Within one `ped_update_document`, an `AnnotationFlow` added *before* a
`SequenceFlow` is stored *after* it.~~ This was the general reversal described
below, not something about note lines.

### Microflow canvas: what one update can address (measured live 2026-10-21, 11.15.0-rc.4)

**Fixed: ALTER MICROFLOW insert over MCP failed on 11.15.** Every insert failed
with `undefined is not an object (evaluating 'u.$Type')`, with or without a note.
Direct `ped_update_document` probes on bare test documents found two rules behind
it. Both follow from the tool's own description: *"every `index` you provide refers
to the array as it exists BEFORE this call"*.

- **An update cannot address an element it adds.** A path resolves against the
document as it was before the update, so `/flows/N/…` or `/objectCollection/objects/N/…`
for a new N does not reach the new element:

| Same update as the add | Result |
|---|---|
| set `/flows/N/line/originControlVector` | **fails the whole update** (`u.$Type`): the line is not there yet |
| set `/flows/N/line` (whole `BezierCurve`), then a vector | fails the same way |
| set `/flows/N/line` (whole `BezierCurve`) alone | accepted, **not applied** |
| `line` or `isErrorHandler` in the `SequenceFlow` constructor value | accepted, **ignored** (the constructor has neither) |
| 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**: "No action defined"; the position is the constructor's `x`/`y` |
| `action` in the `ActionActivity` constructor value | accepted, **ignored** (the constructor takes `x`, `y`, `caption` only) |
| add the flow in its element shape (`origin`, `line`, `isErrorHandler`) | refused: `NodeUnableToConvertElement, String, System.Guid`, so `$id()` is not resolved on that path |

**Every one of these sets lands in a later update**: control vectors,
`isErrorHandler` and `action` alike, on a flow or activity the previous update
created. `$id(/…/N)` references *do* resolve to elements added earlier in the
same update.
- **Several adds to one array without an `index` are stored in reverse**, and
`$id()` resolves against that final order. Three objects added in one update came
back as 3, 2, 1, and two flows came back as 2, 1. An inserted IF/ELSE was
therefore wired to the wrong objects, while a one-activity insert (one object, one
flow) hid the problem. Adds given **one shared `index`** (the array's length before
the update) are stored in listed order, which is PED's documented rule for adding
several elements at one position.

**The fix** (`flowPatchOps` / `SaveUnit`) sends an insert as **two updates**. The
first adds the new elements, constructor properties only, each at the stored length
as its `index` and wired with `$id()`, and moves the stored elements to make room.
The second fills in the new elements: actions, split conditions, loop sources,
`isErrorHandler` and control vectors (a `0;0` vector is the schema default and is
not sent).

This gives up some of the one-update atomicity the splice relied on. The first
update is still all or nothing, but **a failed second update leaves the insert in
Studio Pro incomplete**. That is the same contract as `CreateMicroflow`, which
creates and then sets activities. The error says so, and `ped_check_errors` names
what is missing. There is no write that avoids it on 11.15: nothing in the update
that adds an activity can give it its action. A retry is refused by the live/local
index check, so it cannot insert twice.

Verified live: plain, IF/ELSE and note inserts, each compared with the same script
run with the default engine. All flows matched on ends, sides and control vectors
(4, 9 and 6 flows), as did every object's position. The fills read back: actions,
the condition, `true`/`false` cases, and the note's caption and size.
`ped_check_errors` was clean on two successive calls. The script is
`mdl-examples/bug-tests/mcp-alter-insert-addresses-new-elements.mdl`.

Still refused upstream: a custom error handler (`on error … begin … end error`)
over MCP. `isErrorHandler` is settable in a later update, so the splice marks such a
flow in its second update, but MDL cannot reach that path yet.

## Capability gaps (established 11.11, status re-checked each release)

Expand Down
73 changes: 73 additions & 0 deletions mdl-examples/bug-tests/mcp-alter-insert-addresses-new-elements.mdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
mdl 1;
-- ============================================================================
-- ALTER MICROFLOW insert over --mcp lands on Studio Pro 11.15
-- ============================================================================
--
-- The insert reads the stored flow from the local project, so the local .mpr
-- must hold the same base document as Studio Pro. Run part 1 over --mcp
-- (`--mcp http://localhost/mcp --mcp-dial <host>:<port>`, with -p pointing at
-- the local project) BEFORE running it locally: the MCP create checks the
-- local project for an existing document. Then run part 2 the same two ways
-- and compare every flow's ends, connection sides and control vectors (the
-- local .mpr via `mxcli bson dump`, Studio Pro's document via
-- ped_read_document). They must match, and ped_check_errors must be clean.
--
-- Before the fix, on 11.15.0-rc.4:
-- * every insert failed with `undefined is not an object (evaluating
-- 'u.$Type')` — the update set a control vector on a flow it added itself;
-- * without that set, the new activity had "No action defined": PED drops a
-- set on an element added in the same update;
-- * the IF/ELSE was wired to the wrong objects, because several adds to one
-- array in one update are stored in reverse.
-- After the fix, all 4 + 9 + 6 flows below match the default engine.
--
-- Studio Pro's MCP server has no delete, so the documents stay in the open
-- project after a run.
-- ============================================================================

-- Part 1: the base documents.

create microflow MyFirstModule.MCPA_XPlain ()
begin
log info node 'MCPA' 'one';
log info node 'MCPA' 'two';
end;

create microflow MyFirstModule.MCPA_XIf ()
begin
log info node 'MCPA' 'one';
log info node 'MCPA' 'two';
end;

create microflow MyFirstModule.MCPA_XNote ()
begin
log info node 'MCPA' 'one';
log info node 'MCPA' 'two';
end;

-- Part 2: the inserts.

alter microflow MyFirstModule.MCPA_XPlain {
insert after log node 'MCPA' 'one' begin
log info node 'MCPA' 'inserted';
end;
};

alter microflow MyFirstModule.MCPA_XIf {
insert after log node 'MCPA' 'one' begin
declare $N INTEGER = 1;
if $N > 0 then
log info node 'MCPA' 'positive';
else
log info node 'MCPA' 'not positive';
end if;
end;
};

alter microflow MyFirstModule.MCPA_XNote {
insert after log node 'MCPA' 'one' begin
@annotation 'Inserted with a note'
log info node 'MCPA' 'inserted';
log info node 'MCPA' 'and another';
end;
};
Loading
Loading