@nanobpm/nano-workforce 0.156.0 → 0.158.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,306 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:zeebe="http://camunda.org/schema/zeebe/1.0" xmlns:nano="https://nanobpm.io/schema/shapes/1.0" id="Definitions_nano_workforce_wait_gate" targetNamespace="http://nanobpm.io/nano-workforce">
3
+ <bpmn:process id="wait-gate" name="Readiness wait-gate cell" isExecutable="true">
4
+ <bpmn:startEvent id="Start" name="Gate opened">
5
+ <bpmn:outgoing>wg_toProbe</bpmn:outgoing>
6
+ </bpmn:startEvent>
7
+ <bpmn:subProcess id="probe-loop" name="Probe readiness loop">
8
+ <bpmn:incoming>wg_toProbe</bpmn:incoming>
9
+ <bpmn:outgoing>wg_probed</bpmn:outgoing>
10
+ <bpmn:startEvent id="probe-loop-start" name="Start probe loop">
11
+ <bpmn:outgoing>pl_toProbe</bpmn:outgoing>
12
+ </bpmn:startEvent>
13
+ <bpmn:serviceTask id="probe" name="Probe readiness">
14
+ <bpmn:extensionElements>
15
+ <zeebe:taskDefinition type="pr.readiness-probe" />
16
+ <zeebe:properties>
17
+ <zeebe:property name="io.nanobpm.dataEnvelope.in" value="ReadinessProbeIn" />
18
+ <zeebe:property name="io.nanobpm.dataEnvelope.out" value="ReadinessProbeOut" />
19
+ </zeebe:properties>
20
+ </bpmn:extensionElements>
21
+ <bpmn:incoming>pl_toProbe</bpmn:incoming>
22
+ <bpmn:incoming>pl_pollBack</bpmn:incoming>
23
+ <bpmn:outgoing>pl_probed</bpmn:outgoing>
24
+ </bpmn:serviceTask>
25
+ <bpmn:exclusiveGateway id="probe-ready-gw" name="ready?" default="pl_notReady">
26
+ <bpmn:incoming>pl_probed</bpmn:incoming>
27
+ <bpmn:outgoing>pl_ready</bpmn:outgoing>
28
+ <bpmn:outgoing>pl_notReady</bpmn:outgoing>
29
+ </bpmn:exclusiveGateway>
30
+ <bpmn:intermediateCatchEvent id="wait-poll" name="Wait poll interval">
31
+ <bpmn:incoming>pl_notReady</bpmn:incoming>
32
+ <bpmn:outgoing>pl_pollBack</bpmn:outgoing>
33
+ <bpmn:timerEventDefinition id="ted_probePollEvery">
34
+ <bpmn:timeDuration xsi:type="bpmn:tFormalExpression">=probePollEvery</bpmn:timeDuration>
35
+ </bpmn:timerEventDefinition>
36
+ </bpmn:intermediateCatchEvent>
37
+ <bpmn:endEvent id="probe-loop-end" name="Probe ready">
38
+ <bpmn:incoming>pl_ready</bpmn:incoming>
39
+ </bpmn:endEvent>
40
+ <bpmn:sequenceFlow id="pl_toProbe" sourceRef="probe-loop-start" targetRef="probe" />
41
+ <bpmn:sequenceFlow id="pl_probed" sourceRef="probe" targetRef="probe-ready-gw" />
42
+ <bpmn:sequenceFlow id="pl_ready" name="ready" sourceRef="probe-ready-gw" targetRef="probe-loop-end">
43
+ <bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">=ready = true</bpmn:conditionExpression>
44
+ </bpmn:sequenceFlow>
45
+ <bpmn:sequenceFlow id="pl_notReady" name="not ready" sourceRef="probe-ready-gw" targetRef="wait-poll" />
46
+ <bpmn:sequenceFlow id="pl_pollBack" sourceRef="wait-poll" targetRef="probe" />
47
+ </bpmn:subProcess>
48
+ <bpmn:boundaryEvent id="probe-timeout" name="Gate timed out" attachedToRef="probe-loop">
49
+ <bpmn:outgoing>wg_probeTimeout</bpmn:outgoing>
50
+ <bpmn:timerEventDefinition id="ted_probeTimeout">
51
+ <bpmn:timeDuration xsi:type="bpmn:tFormalExpression">=probeTimeout</bpmn:timeDuration>
52
+ </bpmn:timerEventDefinition>
53
+ </bpmn:boundaryEvent>
54
+ <bpmn:serviceTask id="probe-last-attempt" name="Probe readiness (last attempt)">
55
+ <bpmn:extensionElements>
56
+ <zeebe:taskDefinition type="pr.readiness-probe" />
57
+ <zeebe:properties>
58
+ <zeebe:property name="io.nanobpm.dataEnvelope.in" value="ReadinessProbeIn" />
59
+ <zeebe:property name="io.nanobpm.dataEnvelope.out" value="ReadinessProbeOut" />
60
+ </zeebe:properties>
61
+ <zeebe:ioMapping>
62
+ <zeebe:input source="=true" target="lastAttempt" />
63
+ </zeebe:ioMapping>
64
+ </bpmn:extensionElements>
65
+ <bpmn:incoming>wg_probeTimeout</bpmn:incoming>
66
+ <bpmn:outgoing>wg_lastAttemptDone</bpmn:outgoing>
67
+ </bpmn:serviceTask>
68
+ <bpmn:exclusiveGateway id="gw-ready" name="ready?" default="wg_escalate">
69
+ <bpmn:incoming>wg_probed</bpmn:incoming>
70
+ <bpmn:incoming>wg_lastAttemptDone</bpmn:incoming>
71
+ <bpmn:outgoing>wg_ready</bpmn:outgoing>
72
+ <bpmn:outgoing>wg_escalate</bpmn:outgoing>
73
+ </bpmn:exclusiveGateway>
74
+ <bpmn:userTask id="readiness-escalation" name="Readiness escalation (human)">
75
+ <bpmn:extensionElements>
76
+ <zeebe:formDefinition formId="readiness-escalation" />
77
+ <zeebe:userTask />
78
+ <zeebe:assignmentDefinition candidateGroups="operators" />
79
+ </bpmn:extensionElements>
80
+ <bpmn:incoming>wg_escalate</bpmn:incoming>
81
+ <bpmn:outgoing>wg_toResolution</bpmn:outgoing>
82
+ </bpmn:userTask>
83
+ <bpmn:boundaryEvent id="be_wg_sla" name="SLA elapsed" attachedToRef="readiness-escalation">
84
+ <bpmn:outgoing>wg_sla</bpmn:outgoing>
85
+ <bpmn:timerEventDefinition id="ted_wg_sla">
86
+ <bpmn:timeDuration xsi:type="bpmn:tFormalExpression">=escalationSlaTimeout</bpmn:timeDuration>
87
+ </bpmn:timerEventDefinition>
88
+ </bpmn:boundaryEvent>
89
+ <bpmn:exclusiveGateway id="gw-resolution" name="resolution?" default="wg_proceed">
90
+ <bpmn:incoming>wg_toResolution</bpmn:incoming>
91
+ <bpmn:outgoing>wg_proceed</bpmn:outgoing>
92
+ <bpmn:outgoing>wg_abandon</bpmn:outgoing>
93
+ </bpmn:exclusiveGateway>
94
+ <bpmn:endEvent id="gate-ready" name="Ready">
95
+ <bpmn:extensionElements>
96
+ <zeebe:ioMapping>
97
+ <zeebe:input source="=&#34;ready&#34;" target="gateOutcome" />
98
+ </zeebe:ioMapping>
99
+ </bpmn:extensionElements>
100
+ <bpmn:incoming>wg_ready</bpmn:incoming>
101
+ <bpmn:incoming>wg_proceed</bpmn:incoming>
102
+ </bpmn:endEvent>
103
+ <bpmn:endEvent id="gate-abandoned" name="Abandoned">
104
+ <bpmn:extensionElements>
105
+ <zeebe:ioMapping>
106
+ <zeebe:input source="=&#34;abandoned&#34;" target="gateOutcome" />
107
+ </zeebe:ioMapping>
108
+ </bpmn:extensionElements>
109
+ <bpmn:incoming>wg_abandon</bpmn:incoming>
110
+ <bpmn:incoming>wg_sla</bpmn:incoming>
111
+ </bpmn:endEvent>
112
+ <bpmn:sequenceFlow id="wg_toProbe" sourceRef="Start" targetRef="probe-loop" />
113
+ <bpmn:sequenceFlow id="wg_probed" sourceRef="probe-loop" targetRef="gw-ready" />
114
+ <bpmn:sequenceFlow id="wg_probeTimeout" name="timed out" sourceRef="probe-timeout" targetRef="probe-last-attempt" />
115
+ <bpmn:sequenceFlow id="wg_lastAttemptDone" sourceRef="probe-last-attempt" targetRef="gw-ready" />
116
+ <bpmn:sequenceFlow id="wg_ready" name="ready" sourceRef="gw-ready" targetRef="gate-ready">
117
+ <bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">=ready = true</bpmn:conditionExpression>
118
+ </bpmn:sequenceFlow>
119
+ <bpmn:sequenceFlow id="wg_escalate" name="stalled" sourceRef="gw-ready" targetRef="readiness-escalation" />
120
+ <bpmn:sequenceFlow id="wg_toResolution" sourceRef="readiness-escalation" targetRef="gw-resolution" />
121
+ <bpmn:sequenceFlow id="wg_proceed" name="acknowledge" sourceRef="gw-resolution" targetRef="gate-ready" />
122
+ <bpmn:sequenceFlow id="wg_abandon" name="abandon" sourceRef="gw-resolution" targetRef="gate-abandoned">
123
+ <bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">=resolution = "abandon"</bpmn:conditionExpression>
124
+ </bpmn:sequenceFlow>
125
+ <bpmn:sequenceFlow id="wg_sla" name="SLA auto-abandon" sourceRef="be_wg_sla" targetRef="gate-abandoned" />
126
+ </bpmn:process>
127
+ <bpmndi:BPMNDiagram id="BPMNDiagram_wait-gate">
128
+ <bpmndi:BPMNPlane id="BPMNPlane_wait-gate" bpmnElement="wait-gate">
129
+ <bpmndi:BPMNShape id="BPMNShape_Start" bpmnElement="Start">
130
+ <dc:Bounds x="80" y="102" width="36" height="36" />
131
+ <bpmndi:BPMNLabel>
132
+ <dc:Bounds x="58" y="143" width="80" height="14" />
133
+ </bpmndi:BPMNLabel>
134
+ </bpmndi:BPMNShape>
135
+ <bpmndi:BPMNShape id="BPMNShape_probe-loop" bpmnElement="probe-loop">
136
+ <dc:Bounds x="216" y="80" width="100" height="80" />
137
+ </bpmndi:BPMNShape>
138
+ <bpmndi:BPMNShape id="BPMNShape_probe-last-attempt" bpmnElement="probe-last-attempt">
139
+ <dc:Bounds x="416" y="240" width="100" height="80" />
140
+ </bpmndi:BPMNShape>
141
+ <bpmndi:BPMNShape id="BPMNShape_gw-ready" bpmnElement="gw-ready" isMarkerVisible="true">
142
+ <dc:Bounds x="616" y="95" width="50" height="50" />
143
+ <bpmndi:BPMNLabel>
144
+ <dc:Bounds x="618" y="76" width="46" height="14" />
145
+ </bpmndi:BPMNLabel>
146
+ </bpmndi:BPMNShape>
147
+ <bpmndi:BPMNShape id="BPMNShape_readiness-escalation" bpmnElement="readiness-escalation">
148
+ <dc:Bounds x="766" y="80" width="100" height="80" />
149
+ </bpmndi:BPMNShape>
150
+ <bpmndi:BPMNShape id="BPMNShape_gw-resolution" bpmnElement="gw-resolution" isMarkerVisible="true">
151
+ <dc:Bounds x="966" y="95" width="50" height="50" />
152
+ <bpmndi:BPMNLabel>
153
+ <dc:Bounds x="951" y="76" width="81" height="14" />
154
+ </bpmndi:BPMNLabel>
155
+ </bpmndi:BPMNShape>
156
+ <bpmndi:BPMNShape id="BPMNShape_gate-ready" bpmnElement="gate-ready">
157
+ <dc:Bounds x="1116" y="102" width="36" height="36" />
158
+ <bpmndi:BPMNLabel>
159
+ <dc:Bounds x="1114" y="83" width="41" height="14" />
160
+ </bpmndi:BPMNLabel>
161
+ </bpmndi:BPMNShape>
162
+ <bpmndi:BPMNShape id="BPMNShape_gate-abandoned" bpmnElement="gate-abandoned">
163
+ <dc:Bounds x="1116" y="262" width="36" height="36" />
164
+ <bpmndi:BPMNLabel>
165
+ <dc:Bounds x="1100" y="303" width="69" height="14" />
166
+ </bpmndi:BPMNLabel>
167
+ </bpmndi:BPMNShape>
168
+ <bpmndi:BPMNShape id="BPMNShape_probe-timeout" bpmnElement="probe-timeout">
169
+ <dc:Bounds x="248" y="142" width="36" height="36" />
170
+ <bpmndi:BPMNLabel>
171
+ <dc:Bounds x="168" y="183" width="76" height="28" />
172
+ </bpmndi:BPMNLabel>
173
+ </bpmndi:BPMNShape>
174
+ <bpmndi:BPMNShape id="BPMNShape_be_wg_sla" bpmnElement="be_wg_sla">
175
+ <dc:Bounds x="798" y="142" width="36" height="36" />
176
+ <bpmndi:BPMNLabel>
177
+ <dc:Bounds x="714" y="223" width="84" height="14" />
178
+ </bpmndi:BPMNLabel>
179
+ </bpmndi:BPMNShape>
180
+ <bpmndi:BPMNEdge id="BPMNEdge_wg_toProbe" bpmnElement="wg_toProbe">
181
+ <di:waypoint x="116" y="120" />
182
+ <di:waypoint x="216" y="120" />
183
+ </bpmndi:BPMNEdge>
184
+ <bpmndi:BPMNEdge id="BPMNEdge_wg_probed" bpmnElement="wg_probed">
185
+ <di:waypoint x="316" y="120" />
186
+ <di:waypoint x="616" y="120" />
187
+ </bpmndi:BPMNEdge>
188
+ <bpmndi:BPMNEdge id="BPMNEdge_wg_escalate" bpmnElement="wg_escalate">
189
+ <di:waypoint x="666" y="120" />
190
+ <di:waypoint x="766" y="120" />
191
+ <bpmndi:BPMNLabel>
192
+ <dc:Bounds x="690" y="98" width="53" height="14" />
193
+ </bpmndi:BPMNLabel>
194
+ </bpmndi:BPMNEdge>
195
+ <bpmndi:BPMNEdge id="BPMNEdge_wg_toResolution" bpmnElement="wg_toResolution">
196
+ <di:waypoint x="866" y="120" />
197
+ <di:waypoint x="966" y="120" />
198
+ </bpmndi:BPMNEdge>
199
+ <bpmndi:BPMNEdge id="BPMNEdge_wg_proceed" bpmnElement="wg_proceed">
200
+ <di:waypoint x="1016" y="120" />
201
+ <di:waypoint x="1116" y="120" />
202
+ <bpmndi:BPMNLabel>
203
+ <dc:Bounds x="1024" y="128" width="84" height="14" />
204
+ </bpmndi:BPMNLabel>
205
+ </bpmndi:BPMNEdge>
206
+ <bpmndi:BPMNEdge id="BPMNEdge_wg_abandon" bpmnElement="wg_abandon">
207
+ <di:waypoint x="991" y="145" />
208
+ <di:waypoint x="991" y="280" />
209
+ <di:waypoint x="1116" y="280" />
210
+ <bpmndi:BPMNLabel>
211
+ <dc:Bounds x="996" y="206" width="53" height="14" />
212
+ </bpmndi:BPMNLabel>
213
+ </bpmndi:BPMNEdge>
214
+ <bpmndi:BPMNEdge id="BPMNEdge_wg_lastAttemptDone" bpmnElement="wg_lastAttemptDone">
215
+ <di:waypoint x="516" y="280" />
216
+ <di:waypoint x="641" y="280" />
217
+ <di:waypoint x="641" y="145" />
218
+ </bpmndi:BPMNEdge>
219
+ <bpmndi:BPMNEdge id="BPMNEdge_wg_ready" bpmnElement="wg_ready">
220
+ <di:waypoint x="641" y="145" />
221
+ <di:waypoint x="641" y="200" />
222
+ <di:waypoint x="1134" y="200" />
223
+ <di:waypoint x="1134" y="138" />
224
+ <bpmndi:BPMNLabel>
225
+ <dc:Bounds x="868" y="178" width="39" height="14" />
226
+ </bpmndi:BPMNLabel>
227
+ </bpmndi:BPMNEdge>
228
+ <bpmndi:BPMNEdge id="BPMNEdge_wg_probeTimeout" bpmnElement="wg_probeTimeout">
229
+ <di:waypoint x="266" y="178" />
230
+ <di:waypoint x="266" y="280" />
231
+ <di:waypoint x="416" y="280" />
232
+ <bpmndi:BPMNLabel>
233
+ <dc:Bounds x="271" y="222" width="67" height="14" />
234
+ </bpmndi:BPMNLabel>
235
+ </bpmndi:BPMNEdge>
236
+ <bpmndi:BPMNEdge id="BPMNEdge_wg_sla" bpmnElement="wg_sla">
237
+ <di:waypoint x="816" y="178" />
238
+ <di:waypoint x="816" y="280" />
239
+ <di:waypoint x="1116" y="280" />
240
+ <bpmndi:BPMNLabel>
241
+ <dc:Bounds x="821" y="215" width="88" height="28" />
242
+ </bpmndi:BPMNLabel>
243
+ </bpmndi:BPMNEdge>
244
+ </bpmndi:BPMNPlane>
245
+ </bpmndi:BPMNDiagram>
246
+ <bpmndi:BPMNDiagram id="BPMNDiagram_probe-loop">
247
+ <bpmndi:BPMNPlane id="BPMNPlane_probe-loop" bpmnElement="probe-loop">
248
+ <bpmndi:BPMNShape id="BPMNShape_probe-loop-start" bpmnElement="probe-loop-start">
249
+ <dc:Bounds x="80" y="102" width="36" height="36" />
250
+ <bpmndi:BPMNLabel>
251
+ <dc:Bounds x="58" y="143" width="80" height="28" />
252
+ </bpmndi:BPMNLabel>
253
+ </bpmndi:BPMNShape>
254
+ <bpmndi:BPMNShape id="BPMNShape_probe" bpmnElement="probe">
255
+ <dc:Bounds x="216" y="80" width="100" height="80" />
256
+ </bpmndi:BPMNShape>
257
+ <bpmndi:BPMNShape id="BPMNShape_probe-ready-gw" bpmnElement="probe-ready-gw" isMarkerVisible="true">
258
+ <dc:Bounds x="416" y="95" width="50" height="50" />
259
+ <bpmndi:BPMNLabel>
260
+ <dc:Bounds x="418" y="76" width="46" height="14" />
261
+ </bpmndi:BPMNLabel>
262
+ </bpmndi:BPMNShape>
263
+ <bpmndi:BPMNShape id="BPMNShape_wait-poll" bpmnElement="wait-poll">
264
+ <dc:Bounds x="566" y="262" width="36" height="36" />
265
+ <bpmndi:BPMNLabel>
266
+ <dc:Bounds x="550" y="229" width="68" height="28" />
267
+ </bpmndi:BPMNLabel>
268
+ </bpmndi:BPMNShape>
269
+ <bpmndi:BPMNShape id="BPMNShape_probe-loop-end" bpmnElement="probe-loop-end">
270
+ <dc:Bounds x="566" y="102" width="36" height="36" />
271
+ <bpmndi:BPMNLabel>
272
+ <dc:Bounds x="544" y="143" width="80" height="14" />
273
+ </bpmndi:BPMNLabel>
274
+ </bpmndi:BPMNShape>
275
+ <bpmndi:BPMNEdge id="BPMNEdge_pl_toProbe" bpmnElement="pl_toProbe">
276
+ <di:waypoint x="116" y="120" />
277
+ <di:waypoint x="216" y="120" />
278
+ </bpmndi:BPMNEdge>
279
+ <bpmndi:BPMNEdge id="BPMNEdge_pl_probed" bpmnElement="pl_probed">
280
+ <di:waypoint x="316" y="120" />
281
+ <di:waypoint x="416" y="120" />
282
+ </bpmndi:BPMNEdge>
283
+ <bpmndi:BPMNEdge id="BPMNEdge_pl_ready" bpmnElement="pl_ready">
284
+ <di:waypoint x="466" y="120" />
285
+ <di:waypoint x="566" y="120" />
286
+ <bpmndi:BPMNLabel>
287
+ <dc:Bounds x="497" y="98" width="39" height="14" />
288
+ </bpmndi:BPMNLabel>
289
+ </bpmndi:BPMNEdge>
290
+ <bpmndi:BPMNEdge id="BPMNEdge_pl_notReady" bpmnElement="pl_notReady">
291
+ <di:waypoint x="441" y="145" />
292
+ <di:waypoint x="441" y="280" />
293
+ <di:waypoint x="566" y="280" />
294
+ <bpmndi:BPMNLabel>
295
+ <dc:Bounds x="446" y="206" width="64" height="14" />
296
+ </bpmndi:BPMNLabel>
297
+ </bpmndi:BPMNEdge>
298
+ <bpmndi:BPMNEdge id="BPMNEdge_pl_pollBack" bpmnElement="pl_pollBack">
299
+ <di:waypoint x="584" y="298" />
300
+ <di:waypoint x="584" y="318" />
301
+ <di:waypoint x="266" y="318" />
302
+ <di:waypoint x="266" y="160" />
303
+ </bpmndi:BPMNEdge>
304
+ </bpmndi:BPMNPlane>
305
+ </bpmndi:BPMNDiagram>
306
+ </bpmn:definitions>
@@ -51,11 +51,16 @@ test("every corpus model is either ported or has a documented blocker", () => {
51
51
  "merge-loop",
52
52
  "plan-fanout",
53
53
  "delivery-human",
54
+ "implement-cell",
55
+ "converge-cell",
56
+ "merge-cell",
57
+ "wait-gate",
58
+ "human-escalation",
54
59
  ];
55
60
  assertEquals(
56
61
  PORTS.map((p) => p.model),
57
62
  expected,
58
- "PORTS must cover all eight goldens in the epic's authoring order",
63
+ "PORTS must cover all corpus goldens in the epic's authoring order",
59
64
  );
60
65
  for (const port of PORTS) {
61
66
  assert(
@@ -187,4 +187,32 @@ export const PORTS: readonly PortEntry[] = [
187
187
  model: "delivery-human",
188
188
  blockedReason: `${MULTI_START_END_BLOCK} (delivery-human: 1 start, 2 ends)`,
189
189
  },
190
+ {
191
+ model: "implement-cell",
192
+ blockedReason:
193
+ "blocked (call activity + multi-end): the published @nanobpm/workflow " +
194
+ "builder emits neither a `callActivity` (this cell delegates escalation to " +
195
+ "`human-escalation` via `zeebe:calledElement`) nor multiple top-level ends " +
196
+ "(implement-cell: 1 start, 1 end but a call-activity body); a shared ADR 0006 " +
197
+ "S4 fine-grained cell composed into feature/plan-fanout by callActivity.",
198
+ },
199
+ {
200
+ model: "converge-cell",
201
+ blockedReason:
202
+ "blocked (shared S4 cell): a hand-authored ADR 0006 S4 fine-grained cell " +
203
+ "(converge handoff) composed into feature by callActivity; not yet ported to " +
204
+ "a @nanobpm/workflow `defineFlow`.",
205
+ },
206
+ {
207
+ model: "merge-cell",
208
+ blockedReason: `${MULTI_START_END_BLOCK} (merge-cell: 1 start, 2 ends)`,
209
+ },
210
+ {
211
+ model: "wait-gate",
212
+ blockedReason: `${MULTI_START_END_BLOCK} (wait-gate: 1 start, 2 ends)`,
213
+ },
214
+ {
215
+ model: "human-escalation",
216
+ blockedReason: `${MULTI_START_END_BLOCK} (human-escalation: 1 start, 3 ends)`,
217
+ },
190
218
  ];
@@ -132,6 +132,17 @@ test("readConvergeInput: parses pr; convergeOnly defaults from the target; depen
132
132
  assertEquals(conv.convergeOnly, true);
133
133
  });
134
134
 
135
+ test("readConvergeInput: `merge-main` (graph-level two-level merge, S5) parses pr and defaults convergeOnly false", () => {
136
+ // The graph-level top-level enrollment target drives the merge loop like `converge-merge`,
137
+ // so its worker-level `readConvergeInput` default must also be `convergeOnly=false`.
138
+ const mm = readConvergeInput("merge-main", { pr: "owner/repo#7" }, null);
139
+ assertEquals(mm.parsed.prKey, "owner/repo#7");
140
+ assertEquals(mm.convergeOnly, false);
141
+ assertEquals(mm.dependsOn, []);
142
+ // An explicit payload override still wins over the target default.
143
+ assertEquals(readConvergeInput("merge-main", { pr: "owner/repo#7", convergeOnly: true }, null).convergeOnly, true);
144
+ });
145
+
135
146
  test("readConvergeInput: an explicit payload.convergeOnly overrides the target default; dependsOn threads through", () => {
136
147
  const r = readConvergeInput("converge-merge", { pr: "owner/repo#7", convergeOnly: true, dependsOn: ["owner/repo#5", 42] as unknown as string[] }, null);
137
148
  assertEquals(r.convergeOnly, true, "the explicit boolean wins over the target default");
@@ -77,9 +77,9 @@ export function safeStringify(value: unknown): string {
77
77
  }
78
78
 
79
79
  /** Parse + validate the converge connector's payload (`{ pr, convergeOnly?, dependsOn? }`) for a
80
- * `converge` / `converge-merge` target. `pr` is REQUIRED and must parse to a canonical `owner/repo#N`
80
+ * `converge` / `converge-merge` / `merge-main` target. `pr` is REQUIRED and must parse to a canonical `owner/repo#N`
81
81
  * (fail CLOSED — a converge connector with no target PR is meaningless and could never enroll).
82
- * `convergeOnly` DEFAULTS from the target (`converge` → review-only `true`; `converge-merge` → drive
82
+ * `convergeOnly` DEFAULTS from the target (`converge` → review-only `true`; `converge-merge`/`merge-main` → drive
83
83
  * the merge loop `false`) and may be overridden per-dispatch by an explicit boolean. `dependsOn` is an
84
84
  * optional list of PR refs unioned into the enrolled PR's merge-stage dependency set (only non-string
85
85
  * entries are dropped; `submitPr` itself ignores unparseable refs). Exported for unit coverage.