@nanobpm/nano-workforce 0.63.0 → 0.65.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/app/agentCompletion.test.ts +51 -0
  3. package/app/agentCompletion.ts +50 -12
  4. package/app/feature.ts +80 -2
  5. package/app/featureEscalation.test.ts +180 -0
  6. package/app/service.ts +45 -1
  7. package/db/migrations/031_feature_escalation_surface.sql +28 -0
  8. package/e2e/feature-run.e2e.ts +58 -0
  9. package/nano.app.json +6 -1
  10. package/openapi.yaml +52 -0
  11. package/operations/answerFeatureEscalation.ts +68 -0
  12. package/package.json +2 -2
  13. package/pages/feature.page.json +39 -3
  14. package/pages/overview.page.json +37 -2
  15. package/resources/processes/convergence-loop.bpmn +10 -0
  16. package/resources/processes/feature.bpmn +104 -58
  17. package/resources/processes/merge-loop.bpmn +2 -0
  18. package/resources/processes/plan-fanout.bpmn +42 -1
  19. package/resources/processes/retro.bpmn +20 -0
  20. package/scripts/pages-contract.test.ts +1 -1
  21. package/workers/answer-escalation/worker.ts +3 -5
  22. package/workers/arm-merge/worker.ts +3 -3
  23. package/workers/converge-feature/worker.ts +3 -5
  24. package/workers/ensure-base-branch/worker.ts +3 -4
  25. package/workers/finalize/worker.ts +4 -16
  26. package/workers/mark-merged/worker.ts +3 -3
  27. package/workers/merge/worker.ts +3 -11
  28. package/workers/persist-escalation/worker.ts +5 -22
  29. package/workers/persist-round/worker.ts +6 -16
  30. package/workers/record-dependency/worker.ts +5 -4
  31. package/workers/record-feature/worker.ts +9 -6
  32. package/workers/record-feature-escalation/worker.test.ts +74 -0
  33. package/workers/record-feature-escalation/worker.ts +42 -0
  34. package/workers/record-plan/worker.ts +3 -0
  35. package/workers/record-plan-review/worker.ts +3 -6
  36. package/workers/record-results/worker.ts +3 -3
  37. package/workers/record-trial-merge/worker.ts +4 -0
  38. package/workers/record-wave/worker.ts +3 -0
  39. package/workers/resolve-trial-attention/worker.ts +3 -5
  40. package/workers/retro-gather/worker.ts +3 -3
  41. package/workers/retro-record/worker.ts +4 -8
  42. package/workers/select-wave/worker.ts +3 -4
package/nano.app.json CHANGED
@@ -65,7 +65,8 @@
65
65
  "keyField": "process_key",
66
66
  "statusField": "status",
67
67
  "activeStatuses": [
68
- "running"
68
+ "running",
69
+ "escalated"
69
70
  ],
70
71
  "onTerminated": {
71
72
  "set": {
@@ -132,6 +133,10 @@
132
133
  "taskType": "pr.record-feature",
133
134
  "handler": "workers/record-feature/worker.ts"
134
135
  },
136
+ {
137
+ "taskType": "pr.record-feature-escalation",
138
+ "handler": "workers/record-feature-escalation/worker.ts"
139
+ },
135
140
  {
136
141
  "taskType": "pr.record-blocked-ack",
137
142
  "handler": "workers/record-blocked-ack/worker.ts"
package/openapi.yaml CHANGED
@@ -925,6 +925,58 @@ paths:
925
925
  application/json:
926
926
  schema:
927
927
  $ref: "#/components/schemas/MessageResult"
928
+ /actions/answer-escalation:
929
+ post:
930
+ operationId: answerFeatureEscalation
931
+ summary: "Answer a native feature-run escalation (issue #210). Completes the parked
932
+ `feature-escalation` user task with the operator's typed form variables (resolution=answer/abandon
933
+ + guidance), driving the canonical attributed completer (completeUserTaskAttributed) — the same
934
+ resume path the task inbox uses, recording who answered. The poller then reconciles the run off
935
+ `escalated`. This is the nwf UI's answer affordance for a feature run parked on a human decision."
936
+ requestBody:
937
+ required: true
938
+ content:
939
+ application/json:
940
+ schema:
941
+ type: object
942
+ additionalProperties: false
943
+ required:
944
+ - userTaskKey
945
+ - resolution
946
+ properties:
947
+ userTaskKey:
948
+ type: string
949
+ minLength: 1
950
+ description: The parked `feature-escalation` user-task key (feature_runs.escalation_user_task_key).
951
+ resolution:
952
+ type: string
953
+ enum: [answer, abandon]
954
+ description: answer ⇒ re-dispatch the implement task with the guidance; abandon ⇒ give up on the task.
955
+ answer:
956
+ type: string
957
+ description: Guidance for the implementation agent. Required (non-blank) when resolution is answer.
958
+ operator:
959
+ type: string
960
+ description: Optional operator handle recorded in the attribution ledger; defaults to "operator".
961
+ responses:
962
+ "200":
963
+ description: The escalation user task was completed and the process resumed.
964
+ content:
965
+ application/json:
966
+ schema:
967
+ $ref: "#/components/schemas/MessageResult"
968
+ "400":
969
+ description: A required field was missing/invalid, or the target is not an escalation task.
970
+ content:
971
+ application/json:
972
+ schema:
973
+ $ref: "#/components/schemas/MessageResult"
974
+ "404":
975
+ description: No open escalation user task matches the userTaskKey.
976
+ content:
977
+ application/json:
978
+ schema:
979
+ $ref: "#/components/schemas/MessageResult"
928
980
  /hooks/agent-complete:
929
981
  post:
930
982
  operationId: agentCompleteEscalation
@@ -0,0 +1,68 @@
1
+ // POST /app/api/actions/answer-escalation → operationId `answerFeatureEscalation` (issue #210).
2
+ // The nwf UI's answer affordance for a native feature-run escalation: an operator answers the parked
3
+ // `feature-escalation` user task (resolution=answer/abandon + optional guidance) directly from the
4
+ // Feature / Overview pages, instead of only from the generic task inbox.
5
+ //
6
+ // It routes through the ONE canonical attributed completer (`completeEscalationAsHuman` →
7
+ // `completeUserTaskAttributed`), so the completion uses the exact same typed `.form` variables and
8
+ // engine resume path a human drives from the task inbox — no parallel completion — while recording
9
+ // WHO answered in the `task_completions` ledger. The poller then reconciles the run off `escalated`
10
+ // (pollFeatureEscalations) once the task is gone.
11
+ //
12
+ // The runtime validates the body against openapi.yaml (`userTaskKey` + `resolution` required); this
13
+ // delegate narrows the validated shape, enforces the answer/abandon contract, and builds the typed
14
+ // completion variables the `feature-escalation` form + the `w_gw_answer` gateway expect.
15
+
16
+ import { completeEscalationAsHuman } from "../app/agentCompletion.ts";
17
+ import { featureRuns } from "../app/feature.ts";
18
+ import { defineOperation } from "../nano-generated/operations.ts";
19
+
20
+ const str = (v: unknown): string => (typeof v === "string" ? v.trim() : "");
21
+
22
+ export default defineOperation("answerFeatureEscalation", async ({ body }, app) => {
23
+ if (!body || typeof body !== "object") {
24
+ app.log.warn("answer-escalation rejected: missing request body");
25
+ return { status: 400, body: { ok: false, error: "userTaskKey and resolution are required" } };
26
+ }
27
+
28
+ const userTaskKey = str(body.userTaskKey);
29
+ if (!userTaskKey) return { status: 400, body: { ok: false, error: "userTaskKey is required" } };
30
+
31
+ const resolution = str(body.resolution);
32
+ if (resolution !== "answer" && resolution !== "abandon") {
33
+ return { status: 400, body: { ok: false, error: "resolution must be 'answer' or 'abandon'" } };
34
+ }
35
+
36
+ // The `feature-escalation` form completes with `{ resolution, answer }`; the `w_gw_answer` gateway
37
+ // re-dispatches implement-task with `answer` when resolution=answer, else routes to record-feature.
38
+ // Guidance is required for an answer (an empty answer re-dispatches the agent with nothing new).
39
+ const answer = str(body.answer);
40
+ if (resolution === "answer" && !answer) {
41
+ return { status: 400, body: { ok: false, error: "answer (guidance) is required when resolution is 'answer'" } };
42
+ }
43
+ const variables: Record<string, unknown> = resolution === "answer" ? { resolution, answer } : { resolution };
44
+
45
+ // The completing operator, for the attribution ledger. Optional — the UI has no per-operator auth,
46
+ // so default to a generic handle rather than blocking the answer.
47
+ const operatorId = str(body.operator) || "operator";
48
+
49
+ const r = await completeEscalationAsHuman(app.data, app.engine, { userTaskKey, operatorId, variables });
50
+ if (r.ok) {
51
+ // Reconcile this operation's OWN action immediately: clear the denormalised escalation pointer +
52
+ // question so the pages stop offering an answer affordance for a task that is now completed. Leave
53
+ // `status` to the poller (escalated → running on the answer loop) / record-feature (terminal on
54
+ // abandon), so we never overwrite a status the resumed run has already advanced to.
55
+ for (const run of await featureRuns(app.data).find({ escalation_user_task_key: userTaskKey })) {
56
+ await featureRuns(app.data).update(run.feature_key, {
57
+ escalation_question: null,
58
+ escalation_user_task_key: null,
59
+ updated_at: new Date().toISOString(),
60
+ });
61
+ }
62
+ app.log.info("operator answered feature escalation", { userTaskKey, resolution, elementId: r.elementId });
63
+ return { status: 200, body: { ok: true, completionId: r.completionId, elementId: r.elementId } };
64
+ }
65
+ const status = r.reason === "no open escalation task" ? 404 : 400;
66
+ app.log.warn("answer-escalation: not completed", { userTaskKey, reason: r.reason });
67
+ return { status, body: { ok: false, error: r.reason } };
68
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanobpm/nano-workforce",
3
- "version": "0.63.0",
3
+ "version": "0.65.0",
4
4
  "description": "Nano Workforce — an Agent Graph Orchestration application for Agentic SDLC: durable BPMN processes that coordinate a graph of AI agents across the software delivery lifecycle.",
5
5
  "type": "module",
6
6
  "main": "main.ts",
@@ -51,7 +51,7 @@
51
51
  },
52
52
  "dependencies": {
53
53
  "@nanobpm/agentic": "^0.1.0",
54
- "@nanobpm/urban": "^0.47.0"
54
+ "@nanobpm/urban": "^0.48.0"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@biomejs/biome": "^2.4.11",
@@ -60,16 +60,17 @@
60
60
  "source": "app",
61
61
  "table": "feature_runs",
62
62
  "orderBy": { "field": "updated_at", "dir": "desc" },
63
- "filter": [{ "field": "status", "in": ["running", "awaiting_operator"] }]
63
+ "filter": [{ "field": "status", "in": ["running", "escalated", "awaiting_operator"] }]
64
64
  },
65
65
  "tabs": [
66
- { "label": "Active", "filter": [{ "field": "status", "in": ["running", "awaiting_operator"] }] },
66
+ { "label": "Active", "filter": [{ "field": "status", "in": ["running", "escalated", "awaiting_operator"] }] },
67
67
  { "label": "History", "filter": [{ "field": "status", "in": ["opened", "converging", "merged", "converged", "blocked", "skipped", "failed", "abandoned"] }] },
68
68
  { "label": "All", "filter": [] }
69
69
  ],
70
70
  "columns": [
71
71
  { "field": "feature_key", "header": "Feature", "linkField": "issue_url" },
72
72
  { "field": "status", "header": "Status", "link": { "kind": "processExplorer", "keyField": "process_key" } },
73
+ { "field": "escalation_question", "header": "Escalation" },
73
74
  { "field": "base_branch", "header": "Base branch" },
74
75
  { "field": "pr_key", "header": "PR", "link": { "kind": "page", "page": "home", "keyField": "pr_key" } },
75
76
  { "field": "delivery_label", "header": "Delivery" },
@@ -77,7 +78,42 @@
77
78
  { "field": "auto_merge", "header": "Auto-merge" },
78
79
  { "field": "outcome", "header": "Outcome" },
79
80
  { "field": "updated_at", "header": "Updated" }
80
- ]
81
+ ],
82
+ "rowActions": [
83
+ {
84
+ "label": "Abandon",
85
+ "confirm": "Abandon this escalated task? The run gives up on it (no PR).",
86
+ "showWhenField": "escalation_user_task_key",
87
+ "action": {
88
+ "path": "/app/api/actions/answer-escalation",
89
+ "body": { "userTaskKey": "{{row.escalation_user_task_key}}", "resolution": "abandon" }
90
+ }
91
+ }
92
+ ],
93
+ "detail": {
94
+ "fields": [
95
+ { "field": "escalation_question", "label": "Escalation question" },
96
+ { "field": "outcome", "label": "Outcome" },
97
+ { "field": "delivery_label", "label": "Delivery" }
98
+ ],
99
+ "form": {
100
+ "showWhenField": "escalation_user_task_key",
101
+ "title": "Answer escalation",
102
+ "promptField": "escalation_question",
103
+ "inputKey": "answer",
104
+ "inputLabel": "Guidance for the implementation agent",
105
+ "submitLabel": "Answer & re-dispatch",
106
+ "action": {
107
+ "path": "/app/api/actions/answer-escalation",
108
+ "successLabel": "Answered — the agent will resume",
109
+ "body": {
110
+ "userTaskKey": "{{row.escalation_user_task_key}}",
111
+ "resolution": "answer",
112
+ "answer": "{{form.answer}}"
113
+ }
114
+ }
115
+ }
116
+ }
81
117
  }
82
118
  }
83
119
  ]
@@ -119,18 +119,53 @@
119
119
  "source": "app",
120
120
  "table": "feature_runs",
121
121
  "orderBy": { "field": "updated_at", "dir": "desc" },
122
- "filter": [{ "field": "status", "in": ["running", "awaiting_operator"] }]
122
+ "filter": [{ "field": "status", "in": ["running", "escalated", "awaiting_operator"] }]
123
123
  },
124
124
  "columns": [
125
125
  { "field": "feature_key", "header": "Feature", "link": { "kind": "page", "page": "feature", "keyField": "feature_key" } },
126
126
  { "field": "status", "header": "Status", "link": { "kind": "processExplorer", "keyField": "process_key" } },
127
+ { "field": "escalation_question", "header": "Escalation" },
127
128
  { "field": "base_branch", "header": "Base branch" },
128
129
  { "field": "pr_key", "header": "PR", "link": { "kind": "page", "page": "home", "keyField": "pr_key" } },
129
130
  { "field": "converge", "header": "Converge" },
130
131
  { "field": "auto_merge", "header": "Auto-merge" },
131
132
  { "field": "outcome", "header": "Outcome" },
132
133
  { "field": "updated_at", "header": "Updated" }
133
- ]
134
+ ],
135
+ "rowActions": [
136
+ {
137
+ "label": "Abandon",
138
+ "confirm": "Abandon this escalated task? The run gives up on it (no PR).",
139
+ "showWhenField": "escalation_user_task_key",
140
+ "action": {
141
+ "path": "/app/api/actions/answer-escalation",
142
+ "body": { "userTaskKey": "{{row.escalation_user_task_key}}", "resolution": "abandon" }
143
+ }
144
+ }
145
+ ],
146
+ "detail": {
147
+ "fields": [
148
+ { "field": "escalation_question", "label": "Escalation question" },
149
+ { "field": "outcome", "label": "Outcome" }
150
+ ],
151
+ "form": {
152
+ "showWhenField": "escalation_user_task_key",
153
+ "title": "Answer escalation",
154
+ "promptField": "escalation_question",
155
+ "inputKey": "answer",
156
+ "inputLabel": "Guidance for the implementation agent",
157
+ "submitLabel": "Answer & re-dispatch",
158
+ "action": {
159
+ "path": "/app/api/actions/answer-escalation",
160
+ "successLabel": "Answered — the agent will resume",
161
+ "body": {
162
+ "userTaskKey": "{{row.escalation_user_task_key}}",
163
+ "resolution": "answer",
164
+ "answer": "{{form.answer}}"
165
+ }
166
+ }
167
+ }
168
+ }
134
169
  }
135
170
  }
136
171
  ]
@@ -28,6 +28,10 @@
28
28
  <nano:extend name="round" type="integer" />
29
29
  <nano:extend name="status" type="string" optional="true" />
30
30
  <nano:extend name="summary" type="string" optional="true" />
31
+ <nano:extend name="repo" type="string" optional="true" />
32
+ <nano:extend name="prNumber" type="integer" optional="true" />
33
+ <nano:extend name="prUrl" type="string" optional="true" />
34
+ <nano:extend name="abandonUrl" type="string" optional="true" />
31
35
  </nano:shape>
32
36
  <nano:shape id="EscalationIn" name="Record escalation — input">
33
37
  <nano:extend name="prKey" type="string" />
@@ -36,6 +40,10 @@
36
40
  <nano:extend name="summary" type="string" optional="true" />
37
41
  <nano:extend name="question" type="string" optional="true" />
38
42
  <nano:extend name="recordRound" type="boolean" optional="true" />
43
+ <nano:extend name="repo" type="string" optional="true" />
44
+ <nano:extend name="prNumber" type="integer" optional="true" />
45
+ <nano:extend name="prUrl" type="string" optional="true" />
46
+ <nano:extend name="abandonUrl" type="string" optional="true" />
39
47
  </nano:shape>
40
48
  <nano:shape id="EscalationOut" name="Record escalation — result">
41
49
  <nano:extend name="escalationId" type="integer" optional="true" />
@@ -53,6 +61,8 @@
53
61
  <nano:extend name="prUrl" type="string" />
54
62
  <nano:extend name="round" type="integer" />
55
63
  <nano:extend name="summary" type="string" optional="true" />
64
+ <nano:extend name="convergeOnly" type="boolean" optional="true" />
65
+ <nano:extend name="abandonUrl" type="string" optional="true" />
56
66
  </nano:shape>
57
67
  <nano:shape id="ReviewReady" name="review-ready message payload">
58
68
  <nano:extend name="reviewId" type="integer" />
@@ -1,12 +1,34 @@
1
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" id="Definitions_nano_workforce_feature" targetNamespace="http://nanobpm.io/nano-workforce">
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_feature" targetNamespace="http://nanobpm.io/nano-workforce">
3
3
  <bpmn:process id="feature" name="Single-issue feature run" isExecutable="true">
4
+ <bpmn:extensionElements>
5
+ <nano:shapes>
6
+ <nano:shape id="RecordFeatureIn" name="Record feature result — input">
7
+ <nano:extend name="featureKey" type="string" />
8
+ <nano:extend name="status" type="string" optional="true" />
9
+ <nano:extend name="pr" type="string" optional="true" />
10
+ <nano:extend name="summary" type="string" optional="true" />
11
+ </nano:shape>
12
+ <nano:shape id="RecordFeatureEscalationIn" name="Record feature escalation — input">
13
+ <nano:extend name="featureKey" type="string" />
14
+ <nano:extend name="question" type="string" optional="true" />
15
+ </nano:shape>
16
+ <nano:shape id="ConvergeFeatureIn" name="Converge feature PR — input">
17
+ <nano:extend name="featureKey" type="string" />
18
+ <nano:extend name="prKey" type="string" optional="true" />
19
+ <nano:extend name="autoMerge" type="boolean" optional="true" />
20
+ </nano:shape>
21
+ </nano:shapes>
22
+ </bpmn:extensionElements>
4
23
  <bpmn:startEvent id="Start" name="Issue submitted">
5
24
  <bpmn:outgoing>f_toEnsureBase</bpmn:outgoing>
6
25
  </bpmn:startEvent>
7
26
  <bpmn:serviceTask id="ensure-base-branch" name="Ensure base branch">
8
27
  <bpmn:extensionElements>
9
28
  <zeebe:taskDefinition type="pr.ensure-base-branch" />
29
+ <zeebe:properties>
30
+ <zeebe:property name="io.nanobpm.dataEnvelope.in" value="EnsureBaseBranchIn" />
31
+ </zeebe:properties>
10
32
  <zeebe:ioMapping>
11
33
  <zeebe:input source="=repo" target="repo" />
12
34
  <zeebe:input source="=baseBranch" target="baseBranch" />
@@ -36,13 +58,23 @@
36
58
  <bpmn:outgoing>w_escalate</bpmn:outgoing>
37
59
  <bpmn:outgoing>w_done</bpmn:outgoing>
38
60
  </bpmn:exclusiveGateway>
61
+ <bpmn:serviceTask id="record-feature-escalation" name="Record escalation">
62
+ <bpmn:extensionElements>
63
+ <zeebe:taskDefinition type="pr.record-feature-escalation" />
64
+ <zeebe:properties>
65
+ <zeebe:property name="io.nanobpm.dataEnvelope.in" value="RecordFeatureEscalationIn" />
66
+ </zeebe:properties>
67
+ </bpmn:extensionElements>
68
+ <bpmn:incoming>w_escalate</bpmn:incoming>
69
+ <bpmn:outgoing>w_toEscalationTask</bpmn:outgoing>
70
+ </bpmn:serviceTask>
39
71
  <bpmn:userTask id="feature-escalation" name="Task escalation (human)">
40
72
  <bpmn:extensionElements>
41
73
  <zeebe:formDefinition formId="feature-escalation" />
42
74
  <zeebe:userTask />
43
- <zeebe:assignmentDefinition candidateGroups="operators" assignee="=escalationAssignee" />
75
+ <zeebe:assignmentDefinition candidateGroups="operators" assignee="=if (is defined(escalationAssignee) and escalationAssignee != null and trim(string(escalationAssignee)) != &#34;&#34;) then escalationAssignee else null" />
44
76
  </bpmn:extensionElements>
45
- <bpmn:incoming>w_escalate</bpmn:incoming>
77
+ <bpmn:incoming>w_toEscalationTask</bpmn:incoming>
46
78
  <bpmn:outgoing>w_toAnswerGw</bpmn:outgoing>
47
79
  </bpmn:userTask>
48
80
  <bpmn:boundaryEvent id="be_feature_sla" name="SLA elapsed" attachedToRef="feature-escalation">
@@ -59,6 +91,9 @@
59
91
  <bpmn:serviceTask id="record-feature" name="Record result">
60
92
  <bpmn:extensionElements>
61
93
  <zeebe:taskDefinition type="pr.record-feature" />
94
+ <zeebe:properties>
95
+ <zeebe:property name="io.nanobpm.dataEnvelope.in" value="RecordFeatureIn" />
96
+ </zeebe:properties>
62
97
  </bpmn:extensionElements>
63
98
  <bpmn:incoming>w_done</bpmn:incoming>
64
99
  <bpmn:incoming>w_abandon</bpmn:incoming>
@@ -98,6 +133,9 @@
98
133
  <bpmn:serviceTask id="converge" name="Hand off to convergence loop">
99
134
  <bpmn:extensionElements>
100
135
  <zeebe:taskDefinition type="pr.converge-feature" />
136
+ <zeebe:properties>
137
+ <zeebe:property name="io.nanobpm.dataEnvelope.in" value="ConvergeFeatureIn" />
138
+ </zeebe:properties>
101
139
  </bpmn:extensionElements>
102
140
  <bpmn:incoming>f_converge</bpmn:incoming>
103
141
  <bpmn:outgoing>f_toEndConverged</bpmn:outgoing>
@@ -110,9 +148,10 @@
110
148
  <bpmn:sequenceFlow id="f_toEnsureBase" sourceRef="Start" targetRef="ensure-base-branch" />
111
149
  <bpmn:sequenceFlow id="f_toImplement" sourceRef="ensure-base-branch" targetRef="implement-task" />
112
150
  <bpmn:sequenceFlow id="w_toGw" sourceRef="implement-task" targetRef="w_gw" />
113
- <bpmn:sequenceFlow id="w_escalate" name="escalated" sourceRef="w_gw" targetRef="feature-escalation">
151
+ <bpmn:sequenceFlow id="w_escalate" name="escalated" sourceRef="w_gw" targetRef="record-feature-escalation">
114
152
  <bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">=status = "escalated" and question != null and trim(question) != ""</bpmn:conditionExpression>
115
153
  </bpmn:sequenceFlow>
154
+ <bpmn:sequenceFlow id="w_toEscalationTask" sourceRef="record-feature-escalation" targetRef="feature-escalation" />
116
155
  <bpmn:sequenceFlow id="w_done" name="done" sourceRef="w_gw" targetRef="record-feature" />
117
156
  <bpmn:sequenceFlow id="w_toAnswerGw" sourceRef="feature-escalation" targetRef="w_gw_answer" />
118
157
  <bpmn:sequenceFlow id="w_answerLoop" name="answer" sourceRef="w_gw_answer" targetRef="implement-task">
@@ -153,49 +192,52 @@
153
192
  <dc:Bounds x="604" y="76" width="74" height="14" />
154
193
  </bpmndi:BPMNLabel>
155
194
  </bpmndi:BPMNShape>
156
- <bpmndi:BPMNShape id="BPMNShape_feature-escalation" bpmnElement="feature-escalation">
195
+ <bpmndi:BPMNShape id="BPMNShape_record-feature-escalation" bpmnElement="record-feature-escalation">
157
196
  <dc:Bounds x="766" y="240" width="100" height="80" />
158
197
  </bpmndi:BPMNShape>
198
+ <bpmndi:BPMNShape id="BPMNShape_feature-escalation" bpmnElement="feature-escalation">
199
+ <dc:Bounds x="966" y="240" width="100" height="80" />
200
+ </bpmndi:BPMNShape>
159
201
  <bpmndi:BPMNShape id="BPMNShape_w_gw_answer" bpmnElement="w_gw_answer" isMarkerVisible="true">
160
- <dc:Bounds x="966" y="255" width="50" height="50" />
202
+ <dc:Bounds x="1166" y="255" width="50" height="50" />
161
203
  <bpmndi:BPMNLabel>
162
- <dc:Bounds x="963" y="236" width="56" height="14" />
204
+ <dc:Bounds x="1163" y="236" width="56" height="14" />
163
205
  </bpmndi:BPMNLabel>
164
206
  </bpmndi:BPMNShape>
165
207
  <bpmndi:BPMNShape id="BPMNShape_record-feature" bpmnElement="record-feature">
166
- <dc:Bounds x="1116" y="80" width="100" height="80" />
208
+ <dc:Bounds x="1316" y="80" width="100" height="80" />
167
209
  </bpmndi:BPMNShape>
168
210
  <bpmndi:BPMNShape id="BPMNShape_gw-blocked" bpmnElement="gw-blocked" isMarkerVisible="true">
169
- <dc:Bounds x="1316" y="95" width="50" height="50" />
211
+ <dc:Bounds x="1516" y="95" width="50" height="50" />
170
212
  <bpmndi:BPMNLabel>
171
- <dc:Bounds x="1311" y="76" width="60" height="14" />
213
+ <dc:Bounds x="1511" y="76" width="60" height="14" />
172
214
  </bpmndi:BPMNLabel>
173
215
  </bpmndi:BPMNShape>
174
216
  <bpmndi:BPMNShape id="BPMNShape_feature-blocked" bpmnElement="feature-blocked">
175
- <dc:Bounds x="1466" y="400" width="100" height="80" />
217
+ <dc:Bounds x="1666" y="400" width="100" height="80" />
176
218
  </bpmndi:BPMNShape>
177
219
  <bpmndi:BPMNShape id="BPMNShape_record-blocked-ack" bpmnElement="record-blocked-ack">
178
- <dc:Bounds x="1666" y="400" width="100" height="80" />
220
+ <dc:Bounds x="1866" y="400" width="100" height="80" />
179
221
  </bpmndi:BPMNShape>
180
222
  <bpmndi:BPMNShape id="BPMNShape_gw-converge" bpmnElement="gw-converge" isMarkerVisible="true">
181
- <dc:Bounds x="1491" y="95" width="50" height="50" />
223
+ <dc:Bounds x="1691" y="95" width="50" height="50" />
182
224
  <bpmndi:BPMNLabel>
183
- <dc:Bounds x="1483" y="76" width="67" height="14" />
225
+ <dc:Bounds x="1683" y="76" width="67" height="14" />
184
226
  </bpmndi:BPMNLabel>
185
227
  </bpmndi:BPMNShape>
186
228
  <bpmndi:BPMNShape id="BPMNShape_converge" bpmnElement="converge">
187
- <dc:Bounds x="1666" y="240" width="100" height="80" />
229
+ <dc:Bounds x="1866" y="240" width="100" height="80" />
188
230
  </bpmndi:BPMNShape>
189
231
  <bpmndi:BPMNShape id="BPMNShape_End" bpmnElement="End">
190
- <dc:Bounds x="1866" y="102" width="36" height="36" />
232
+ <dc:Bounds x="2066" y="102" width="36" height="36" />
191
233
  <bpmndi:BPMNLabel>
192
- <dc:Bounds x="1867" y="83" width="34" height="14" />
234
+ <dc:Bounds x="2067" y="83" width="34" height="14" />
193
235
  </bpmndi:BPMNLabel>
194
236
  </bpmndi:BPMNShape>
195
237
  <bpmndi:BPMNShape id="BPMNShape_be_feature_sla" bpmnElement="be_feature_sla">
196
- <dc:Bounds x="798" y="302" width="36" height="36" />
238
+ <dc:Bounds x="998" y="302" width="36" height="36" />
197
239
  <bpmndi:BPMNLabel>
198
- <dc:Bounds x="774" y="383" width="84" height="14" />
240
+ <dc:Bounds x="974" y="383" width="84" height="14" />
199
241
  </bpmndi:BPMNLabel>
200
242
  </bpmndi:BPMNShape>
201
243
  <bpmndi:BPMNEdge id="BPMNEdge_f_toEnsureBase" bpmnElement="f_toEnsureBase">
@@ -212,35 +254,35 @@
212
254
  </bpmndi:BPMNEdge>
213
255
  <bpmndi:BPMNEdge id="BPMNEdge_w_done" bpmnElement="w_done">
214
256
  <di:waypoint x="666" y="120" />
215
- <di:waypoint x="1116" y="120" />
257
+ <di:waypoint x="1316" y="120" />
216
258
  <bpmndi:BPMNLabel>
217
- <dc:Bounds x="875" y="98" width="32" height="14" />
259
+ <dc:Bounds x="975" y="98" width="32" height="14" />
218
260
  </bpmndi:BPMNLabel>
219
261
  </bpmndi:BPMNEdge>
220
262
  <bpmndi:BPMNEdge id="BPMNEdge_f_toBlockedGw" bpmnElement="f_toBlockedGw">
221
- <di:waypoint x="1216" y="120" />
222
- <di:waypoint x="1316" y="120" />
263
+ <di:waypoint x="1416" y="120" />
264
+ <di:waypoint x="1516" y="120" />
223
265
  </bpmndi:BPMNEdge>
224
266
  <bpmndi:BPMNEdge id="BPMNEdge_f_notBlocked" bpmnElement="f_notBlocked">
225
- <di:waypoint x="1366" y="120" />
226
- <di:waypoint x="1491" y="120" />
267
+ <di:waypoint x="1566" y="120" />
268
+ <di:waypoint x="1691" y="120" />
227
269
  <bpmndi:BPMNLabel>
228
- <dc:Bounds x="1390" y="98" width="78" height="14" />
270
+ <dc:Bounds x="1590" y="98" width="78" height="14" />
229
271
  </bpmndi:BPMNLabel>
230
272
  </bpmndi:BPMNEdge>
231
273
  <bpmndi:BPMNEdge id="BPMNEdge_f_noConverge" bpmnElement="f_noConverge">
232
- <di:waypoint x="1541" y="120" />
233
- <di:waypoint x="1866" y="120" />
274
+ <di:waypoint x="1741" y="120" />
275
+ <di:waypoint x="2066" y="120" />
234
276
  <bpmndi:BPMNLabel>
235
- <dc:Bounds x="1668" y="98" width="71" height="14" />
277
+ <dc:Bounds x="1868" y="98" width="71" height="14" />
236
278
  </bpmndi:BPMNLabel>
237
279
  </bpmndi:BPMNEdge>
238
280
  <bpmndi:BPMNEdge id="BPMNEdge_w_abandon" bpmnElement="w_abandon">
239
- <di:waypoint x="1016" y="280" />
240
- <di:waypoint x="1166" y="280" />
241
- <di:waypoint x="1166" y="160" />
281
+ <di:waypoint x="1216" y="280" />
282
+ <di:waypoint x="1366" y="280" />
283
+ <di:waypoint x="1366" y="160" />
242
284
  <bpmndi:BPMNLabel>
243
- <dc:Bounds x="1065" y="258" width="53" height="14" />
285
+ <dc:Bounds x="1265" y="258" width="53" height="14" />
244
286
  </bpmndi:BPMNLabel>
245
287
  </bpmndi:BPMNEdge>
246
288
  <bpmndi:BPMNEdge id="BPMNEdge_w_escalate" bpmnElement="w_escalate">
@@ -252,55 +294,59 @@
252
294
  </bpmndi:BPMNLabel>
253
295
  </bpmndi:BPMNEdge>
254
296
  <bpmndi:BPMNEdge id="BPMNEdge_f_blocked" bpmnElement="f_blocked">
255
- <di:waypoint x="1341" y="145" />
256
- <di:waypoint x="1341" y="440" />
257
- <di:waypoint x="1466" y="440" />
297
+ <di:waypoint x="1541" y="145" />
298
+ <di:waypoint x="1541" y="440" />
299
+ <di:waypoint x="1666" y="440" />
258
300
  <bpmndi:BPMNLabel>
259
- <dc:Bounds x="1346" y="286" width="53" height="14" />
301
+ <dc:Bounds x="1546" y="286" width="53" height="14" />
260
302
  </bpmndi:BPMNLabel>
261
303
  </bpmndi:BPMNEdge>
262
304
  <bpmndi:BPMNEdge id="BPMNEdge_f_converge" bpmnElement="f_converge">
263
- <di:waypoint x="1516" y="145" />
264
- <di:waypoint x="1516" y="280" />
265
- <di:waypoint x="1666" y="280" />
305
+ <di:waypoint x="1716" y="145" />
306
+ <di:waypoint x="1716" y="280" />
307
+ <di:waypoint x="1866" y="280" />
266
308
  <bpmndi:BPMNLabel>
267
- <dc:Bounds x="1521" y="206" width="60" height="14" />
309
+ <dc:Bounds x="1721" y="206" width="60" height="14" />
268
310
  </bpmndi:BPMNLabel>
269
311
  </bpmndi:BPMNEdge>
270
- <bpmndi:BPMNEdge id="BPMNEdge_w_toAnswerGw" bpmnElement="w_toAnswerGw">
312
+ <bpmndi:BPMNEdge id="BPMNEdge_w_toEscalationTask" bpmnElement="w_toEscalationTask">
271
313
  <di:waypoint x="866" y="280" />
272
314
  <di:waypoint x="966" y="280" />
273
315
  </bpmndi:BPMNEdge>
316
+ <bpmndi:BPMNEdge id="BPMNEdge_w_toAnswerGw" bpmnElement="w_toAnswerGw">
317
+ <di:waypoint x="1066" y="280" />
318
+ <di:waypoint x="1166" y="280" />
319
+ </bpmndi:BPMNEdge>
274
320
  <bpmndi:BPMNEdge id="BPMNEdge_f_toBlockedAck" bpmnElement="f_toBlockedAck">
275
- <di:waypoint x="1566" y="440" />
276
- <di:waypoint x="1666" y="440" />
321
+ <di:waypoint x="1766" y="440" />
322
+ <di:waypoint x="1866" y="440" />
277
323
  </bpmndi:BPMNEdge>
278
324
  <bpmndi:BPMNEdge id="BPMNEdge_f_blockedEnd" bpmnElement="f_blockedEnd">
279
- <di:waypoint x="1766" y="440" />
280
- <di:waypoint x="1884" y="440" />
281
- <di:waypoint x="1884" y="138" />
325
+ <di:waypoint x="1966" y="440" />
326
+ <di:waypoint x="2084" y="440" />
327
+ <di:waypoint x="2084" y="138" />
282
328
  </bpmndi:BPMNEdge>
283
329
  <bpmndi:BPMNEdge id="BPMNEdge_f_toEndConverged" bpmnElement="f_toEndConverged">
284
- <di:waypoint x="1766" y="280" />
285
- <di:waypoint x="1884" y="280" />
286
- <di:waypoint x="1884" y="138" />
330
+ <di:waypoint x="1966" y="280" />
331
+ <di:waypoint x="2084" y="280" />
332
+ <di:waypoint x="2084" y="138" />
287
333
  </bpmndi:BPMNEdge>
288
334
  <bpmndi:BPMNEdge id="BPMNEdge_w_sla" bpmnElement="w_sla">
289
- <di:waypoint x="816" y="338" />
290
- <di:waypoint x="816" y="358" />
291
- <di:waypoint x="1166" y="358" />
292
- <di:waypoint x="1166" y="160" />
335
+ <di:waypoint x="1016" y="338" />
336
+ <di:waypoint x="1016" y="358" />
337
+ <di:waypoint x="1366" y="358" />
338
+ <di:waypoint x="1366" y="160" />
293
339
  <bpmndi:BPMNLabel>
294
- <dc:Bounds x="1007" y="325" width="88" height="28" />
340
+ <dc:Bounds x="1207" y="325" width="88" height="28" />
295
341
  </bpmndi:BPMNLabel>
296
342
  </bpmndi:BPMNEdge>
297
343
  <bpmndi:BPMNEdge id="BPMNEdge_w_answerLoop" bpmnElement="w_answerLoop">
298
- <di:waypoint x="991" y="305" />
299
- <di:waypoint x="991" y="360" />
344
+ <di:waypoint x="1191" y="305" />
345
+ <di:waypoint x="1191" y="360" />
300
346
  <di:waypoint x="466" y="360" />
301
347
  <di:waypoint x="466" y="160" />
302
348
  <bpmndi:BPMNLabel>
303
- <dc:Bounds x="704" y="338" width="49" height="14" />
349
+ <dc:Bounds x="804" y="338" width="49" height="14" />
304
350
  </bpmndi:BPMNLabel>
305
351
  </bpmndi:BPMNEdge>
306
352
  </bpmndi:BPMNPlane>
@@ -41,6 +41,8 @@
41
41
  <nano:extend name="prKey" type="string" />
42
42
  <nano:extend name="repo" type="string" />
43
43
  <nano:extend name="prNumber" type="integer" />
44
+ <nano:extend name="round" type="integer" optional="true" />
45
+ <nano:extend name="abandonUrl" type="string" optional="true" />
44
46
  </nano:shape>
45
47
  <nano:shape id="MergeAttemptOut" name="Attempt merge — result">
46
48
  <nano:extend name="mergeStatus" type="string" />