@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.
- package/CHANGELOG.md +14 -0
- package/app/agentCompletion.test.ts +51 -0
- package/app/agentCompletion.ts +50 -12
- package/app/feature.ts +80 -2
- package/app/featureEscalation.test.ts +180 -0
- package/app/service.ts +45 -1
- package/db/migrations/031_feature_escalation_surface.sql +28 -0
- package/e2e/feature-run.e2e.ts +58 -0
- package/nano.app.json +6 -1
- package/openapi.yaml +52 -0
- package/operations/answerFeatureEscalation.ts +68 -0
- package/package.json +2 -2
- package/pages/feature.page.json +39 -3
- package/pages/overview.page.json +37 -2
- package/resources/processes/convergence-loop.bpmn +10 -0
- package/resources/processes/feature.bpmn +104 -58
- package/resources/processes/merge-loop.bpmn +2 -0
- package/resources/processes/plan-fanout.bpmn +42 -1
- package/resources/processes/retro.bpmn +20 -0
- package/scripts/pages-contract.test.ts +1 -1
- package/workers/answer-escalation/worker.ts +3 -5
- package/workers/arm-merge/worker.ts +3 -3
- package/workers/converge-feature/worker.ts +3 -5
- package/workers/ensure-base-branch/worker.ts +3 -4
- package/workers/finalize/worker.ts +4 -16
- package/workers/mark-merged/worker.ts +3 -3
- package/workers/merge/worker.ts +3 -11
- package/workers/persist-escalation/worker.ts +5 -22
- package/workers/persist-round/worker.ts +6 -16
- package/workers/record-dependency/worker.ts +5 -4
- package/workers/record-feature/worker.ts +9 -6
- package/workers/record-feature-escalation/worker.test.ts +74 -0
- package/workers/record-feature-escalation/worker.ts +42 -0
- package/workers/record-plan/worker.ts +3 -0
- package/workers/record-plan-review/worker.ts +3 -6
- package/workers/record-results/worker.ts +3 -3
- package/workers/record-trial-merge/worker.ts +4 -0
- package/workers/record-wave/worker.ts +3 -0
- package/workers/resolve-trial-attention/worker.ts +3 -5
- package/workers/retro-gather/worker.ts +3 -3
- package/workers/retro-record/worker.ts +4 -8
- 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.
|
|
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.
|
|
54
|
+
"@nanobpm/urban": "^0.48.0"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@biomejs/biome": "^2.4.11",
|
package/pages/feature.page.json
CHANGED
|
@@ -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
|
]
|
package/pages/overview.page.json
CHANGED
|
@@ -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)) != "") then escalationAssignee else null" />
|
|
44
76
|
</bpmn:extensionElements>
|
|
45
|
-
<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="
|
|
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="
|
|
202
|
+
<dc:Bounds x="1166" y="255" width="50" height="50" />
|
|
161
203
|
<bpmndi:BPMNLabel>
|
|
162
|
-
<dc:Bounds x="
|
|
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="
|
|
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="
|
|
211
|
+
<dc:Bounds x="1516" y="95" width="50" height="50" />
|
|
170
212
|
<bpmndi:BPMNLabel>
|
|
171
|
-
<dc:Bounds x="
|
|
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="
|
|
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="
|
|
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="
|
|
223
|
+
<dc:Bounds x="1691" y="95" width="50" height="50" />
|
|
182
224
|
<bpmndi:BPMNLabel>
|
|
183
|
-
<dc:Bounds x="
|
|
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="
|
|
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="
|
|
232
|
+
<dc:Bounds x="2066" y="102" width="36" height="36" />
|
|
191
233
|
<bpmndi:BPMNLabel>
|
|
192
|
-
<dc:Bounds x="
|
|
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="
|
|
238
|
+
<dc:Bounds x="998" y="302" width="36" height="36" />
|
|
197
239
|
<bpmndi:BPMNLabel>
|
|
198
|
-
<dc:Bounds x="
|
|
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="
|
|
257
|
+
<di:waypoint x="1316" y="120" />
|
|
216
258
|
<bpmndi:BPMNLabel>
|
|
217
|
-
<dc:Bounds x="
|
|
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="
|
|
222
|
-
<di:waypoint x="
|
|
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="
|
|
226
|
-
<di:waypoint x="
|
|
267
|
+
<di:waypoint x="1566" y="120" />
|
|
268
|
+
<di:waypoint x="1691" y="120" />
|
|
227
269
|
<bpmndi:BPMNLabel>
|
|
228
|
-
<dc:Bounds x="
|
|
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="
|
|
233
|
-
<di:waypoint x="
|
|
274
|
+
<di:waypoint x="1741" y="120" />
|
|
275
|
+
<di:waypoint x="2066" y="120" />
|
|
234
276
|
<bpmndi:BPMNLabel>
|
|
235
|
-
<dc:Bounds x="
|
|
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="
|
|
240
|
-
<di:waypoint x="
|
|
241
|
-
<di:waypoint x="
|
|
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="
|
|
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="
|
|
256
|
-
<di:waypoint x="
|
|
257
|
-
<di:waypoint x="
|
|
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="
|
|
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="
|
|
264
|
-
<di:waypoint x="
|
|
265
|
-
<di:waypoint x="
|
|
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="
|
|
309
|
+
<dc:Bounds x="1721" y="206" width="60" height="14" />
|
|
268
310
|
</bpmndi:BPMNLabel>
|
|
269
311
|
</bpmndi:BPMNEdge>
|
|
270
|
-
<bpmndi:BPMNEdge id="
|
|
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="
|
|
276
|
-
<di:waypoint x="
|
|
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="
|
|
280
|
-
<di:waypoint x="
|
|
281
|
-
<di:waypoint x="
|
|
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="
|
|
285
|
-
<di:waypoint x="
|
|
286
|
-
<di:waypoint x="
|
|
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="
|
|
290
|
-
<di:waypoint x="
|
|
291
|
-
<di:waypoint x="
|
|
292
|
-
<di:waypoint x="
|
|
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="
|
|
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="
|
|
299
|
-
<di:waypoint x="
|
|
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="
|
|
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" />
|