@nanobpm/nano-workforce 0.101.0 → 0.101.1
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 +7 -0
- package/app/github.test.ts +36 -1
- package/app/github.ts +35 -2
- package/app/queuedVerdict.test.ts +1 -0
- package/app/service.ts +18 -1
- package/package.json +1 -1
- package/resources/processes/merge-loop.bpmn +174 -148
- package/workers/merge/worker.test.ts +45 -0
- package/workers/merge/worker.ts +32 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [0.101.1](https://github.com/nanobpm/nano-workforce/compare/v0.101.0...v0.101.1) (2026-08-19)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **merge-loop:** abandon a closed-not-merged PR instead of escalating ([#342](https://github.com/nanobpm/nano-workforce/issues/342)) ([#343](https://github.com/nanobpm/nano-workforce/issues/343)) ([10ea6be](https://github.com/nanobpm/nano-workforce/commit/10ea6be0e23c9315dfb71a6c94958826a1795a8c)), closes [#350](https://github.com/nanobpm/nano-workforce/issues/350)
|
|
7
|
+
|
|
1
8
|
# [0.101.0](https://github.com/nanobpm/nano-workforce/compare/v0.100.0...v0.101.0) (2026-08-19)
|
|
2
9
|
|
|
3
10
|
|
package/app/github.test.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// the merge-exclusion graph. Force the token transport and stub `globalThis.fetch`.
|
|
4
4
|
import { test } from "node:test";
|
|
5
5
|
import { assertEquals, assertRejects } from "#test-assert";
|
|
6
|
-
import { BaseBranchMustExistError, coalesceTitle, createPullRequest, ensureBaseBranch, ensurePromotionPr, fetchIssueTitle, fetchPrFiles, isNotAPullRequestError, listPrsForHead } from "./github.ts";
|
|
6
|
+
import { BaseBranchMustExistError, classifyPrLiveness, coalesceTitle, createPullRequest, ensureBaseBranch, ensurePromotionPr, fetchIssueTitle, fetchPrFiles, isNotAPullRequestError, listPrsForHead, type PrState } from "./github.ts";
|
|
7
7
|
|
|
8
8
|
// A fake `fetch` that serves `pages` of file batches; each page N (1-based) returns `pages[N-1]`
|
|
9
9
|
// files (named `f{index}`), setting a `Link: rel="next"` header whenever a later page exists.
|
|
@@ -428,3 +428,38 @@ test("ensurePromotionPr: creates when none exists, then reuses on a re-run (idem
|
|
|
428
428
|
assertEquals(second?.number, 500);
|
|
429
429
|
assertEquals(state.creates.length, 1);
|
|
430
430
|
});
|
|
431
|
+
// The shared PR-liveness gate (#342) maps live GitHub state onto one of {open, merged, closed,
|
|
432
|
+
// unknown} so neither durable loop (merge/convergence) can escalate against a non-open PR. A closed
|
|
433
|
+
// PR is terminal (abandon), a merged PR completes, and a null read stays conservative (unknown →
|
|
434
|
+
// proceed as before). `merged` wins over `state` because a merged PR also reports state="closed".
|
|
435
|
+
function prState(over: Partial<PrState>): PrState {
|
|
436
|
+
return {
|
|
437
|
+
merged: false,
|
|
438
|
+
state: "open",
|
|
439
|
+
mergeStateStatus: "CLEAN",
|
|
440
|
+
failingChecks: 0,
|
|
441
|
+
failingCheckNames: [],
|
|
442
|
+
presentCheckNames: [],
|
|
443
|
+
totalChecks: 0,
|
|
444
|
+
isDraft: false,
|
|
445
|
+
headRefOid: null,
|
|
446
|
+
...over,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
test("classifyPrLiveness: an open PR proceeds", () => {
|
|
451
|
+
assertEquals(classifyPrLiveness(prState({ state: "open" })), "open");
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
test("classifyPrLiveness: a merged PR completes (merged wins over a closed state)", () => {
|
|
455
|
+
assertEquals(classifyPrLiveness(prState({ merged: true, state: "closed" })), "merged");
|
|
456
|
+
assertEquals(classifyPrLiveness(prState({ merged: true, state: "merged" })), "merged");
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
test("classifyPrLiveness: a closed-not-merged PR is terminal (abandon)", () => {
|
|
460
|
+
assertEquals(classifyPrLiveness(prState({ merged: false, state: "closed" })), "closed");
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
test("classifyPrLiveness: a null read (transport hiccup) is unknown — never abandons blind", () => {
|
|
464
|
+
assertEquals(classifyPrLiveness(null), "unknown");
|
|
465
|
+
});
|
package/app/github.ts
CHANGED
|
@@ -484,6 +484,11 @@ export function coalesceTitle(...candidates: (string | null | undefined)[]): str
|
|
|
484
484
|
* failing gates (empty in token mode) so the CI-fix agent knows what to make green. */
|
|
485
485
|
export interface PrState {
|
|
486
486
|
merged: boolean;
|
|
487
|
+
/** GitHub's high-level PR lifecycle state, normalised to `"open" | "closed" | "merged"`. A PR
|
|
488
|
+
* closed *without* merging reports `"closed"` (GitHub also reports a merged PR as `"closed"` on
|
|
489
|
+
* the REST list, but `merged` disambiguates it). Lets a caller gate on PR liveness — see
|
|
490
|
+
* `classifyPrLiveness` — so neither loop escalates against a non-open PR (#342). */
|
|
491
|
+
state: "open" | "closed" | "merged";
|
|
487
492
|
mergeStateStatus: string;
|
|
488
493
|
failingChecks: number;
|
|
489
494
|
failingCheckNames: string[];
|
|
@@ -584,8 +589,10 @@ export async function fetchPrState(
|
|
|
584
589
|
};
|
|
585
590
|
const rollup = j.statusCheckRollup ?? [];
|
|
586
591
|
const names = failingCheckNames(rollup);
|
|
592
|
+
const merged = j.state === "MERGED" || !!j.mergedAt;
|
|
587
593
|
return {
|
|
588
|
-
merged
|
|
594
|
+
merged,
|
|
595
|
+
state: merged ? "merged" : (j.state ?? "").toUpperCase() === "CLOSED" ? "closed" : "open",
|
|
589
596
|
mergeStateStatus: (j.mergeStateStatus || "UNKNOWN").toUpperCase(),
|
|
590
597
|
failingChecks: names.length,
|
|
591
598
|
failingCheckNames: names,
|
|
@@ -604,14 +611,19 @@ export async function fetchPrState(
|
|
|
604
611
|
const j = (await r.json()) as {
|
|
605
612
|
merged?: boolean;
|
|
606
613
|
merged_at?: string | null;
|
|
614
|
+
state?: string;
|
|
607
615
|
mergeable_state?: string;
|
|
608
616
|
draft?: boolean;
|
|
609
617
|
head?: { sha?: string | null };
|
|
610
618
|
};
|
|
619
|
+
const restMerged = !!j.merged || !!j.merged_at;
|
|
611
620
|
return {
|
|
612
621
|
// The single-PR GET returns a `merged` boolean (unlike the list endpoint); we also honour
|
|
613
622
|
// `merged_at` so this mirrors the gh branch's `state === "MERGED" || mergedAt` rule.
|
|
614
|
-
merged:
|
|
623
|
+
merged: restMerged,
|
|
624
|
+
// REST reports a merged PR as `state:"closed"` too, so `merged` disambiguates: a `closed` PR
|
|
625
|
+
// here is genuinely closed WITHOUT merging (e.g. superseded) — the #342 abandon case.
|
|
626
|
+
state: restMerged ? "merged" : (j.state ?? "").toLowerCase() === "closed" ? "closed" : "open",
|
|
615
627
|
mergeStateStatus: normalizeMergeState(j.mergeable_state ?? "unknown"),
|
|
616
628
|
failingChecks: -1, // REST here doesn't enumerate checks → classifier treats BLOCKED as "wait"
|
|
617
629
|
failingCheckNames: [], // …and the CI-fix agent gets no per-check list in token mode
|
|
@@ -622,6 +634,27 @@ export async function fetchPrState(
|
|
|
622
634
|
};
|
|
623
635
|
}
|
|
624
636
|
|
|
637
|
+
/** Map a PR's live GitHub state to one **liveness** verdict shared by both durable loops (merge +
|
|
638
|
+
* convergence), so neither can ever escalate against a non-open PR (#342):
|
|
639
|
+
*
|
|
640
|
+
* • `open` — proceed with the normal protocol.
|
|
641
|
+
* • `merged` — already landed (out-of-band); complete the loop as merged.
|
|
642
|
+
* • `closed` — closed on GitHub WITHOUT merging (e.g. superseded); the PR can never merge, so
|
|
643
|
+
* the loop must **abandon** (terminate) it — NOT escalate a merge no human can
|
|
644
|
+
* complete. This is terminal state, not a human decision.
|
|
645
|
+
* • `unknown` — a transport hiccup left us without live state (`fetchPrState` returned null);
|
|
646
|
+
* stay conservative and fall through to the normal path rather than abandoning a
|
|
647
|
+
* PR we could not read.
|
|
648
|
+
*
|
|
649
|
+
* Deriving all three from one source keeps a single canonical liveness gate instead of each loop
|
|
650
|
+
* re-implementing `pre?.merged`/closed checks against drifting field names. */
|
|
651
|
+
export function classifyPrLiveness(pre: PrState | null): "open" | "merged" | "closed" | "unknown" {
|
|
652
|
+
if (!pre) return "unknown";
|
|
653
|
+
if (pre.merged) return "merged";
|
|
654
|
+
if (pre.state === "closed") return "closed";
|
|
655
|
+
return "open";
|
|
656
|
+
}
|
|
657
|
+
|
|
625
658
|
/** The changed file paths of a PR (for the D2 conflict-scan, #58). `gh` returns them directly;
|
|
626
659
|
* the token transport pages `/pulls/{n}/files` (100/page, capped). Returns `null` when no
|
|
627
660
|
* transport is usable (idle), an empty array for a PR with no files. */
|
package/app/service.ts
CHANGED
|
@@ -25,6 +25,7 @@ import { deriveDelivery, TERMINAL_STATUSES } from "./delivery.ts";
|
|
|
25
25
|
import { backfillFeatureStages, deriveFeatureDelivery, FEATURE_BLOCKED_ELEMENT, FEATURE_ESCALATION_ELEMENT, FEATURE_RUN_STATUSES, type FeatureRunStatus, featureEscalations, featureRuns } from "./feature.ts";
|
|
26
26
|
import {
|
|
27
27
|
classifyMergeability,
|
|
28
|
+
classifyPrLiveness,
|
|
28
29
|
coalesceTitle,
|
|
29
30
|
ensureFreshHeadRun,
|
|
30
31
|
ensurePromotionPr,
|
|
@@ -925,7 +926,8 @@ async function pollMerges(data: DataLayer, engine: EngineClient, token: string)
|
|
|
925
926
|
try {
|
|
926
927
|
const st = await fetchPrState(repo, number, token);
|
|
927
928
|
if (st === null) continue; // no transport → skip this PR (others may still advance)
|
|
928
|
-
|
|
929
|
+
const liveness = classifyPrLiveness(st);
|
|
930
|
+
if (liveness === "merged") {
|
|
929
931
|
// Landed out-of-band (a maintainer clicked Merge, a mergify queue merged it, etc.). The
|
|
930
932
|
// instance is parked at `wait-mergeable`, which subscribes to `merge-ready` — NOT
|
|
931
933
|
// `merge-landed` (that catch, `wait-landed`, only exists later, after we enqueue). Publishing
|
|
@@ -941,6 +943,21 @@ async function pollMerges(data: DataLayer, engine: EngineClient, token: string)
|
|
|
941
943
|
console.log(`[poller] already merged -> ${prKey}`);
|
|
942
944
|
continue;
|
|
943
945
|
}
|
|
946
|
+
if (liveness === "closed") {
|
|
947
|
+
// Closed on GitHub WITHOUT merging (e.g. superseded by a newer PR — #350). The PR can never
|
|
948
|
+
// land, so it must NOT be classified as blocked/conflict and escalated (that orphans the
|
|
949
|
+
// process on a dead PR, #342). Route it through the same canonical `merge-ready` → `ready` →
|
|
950
|
+
// `attempt-merge` path as the merged case; the merge worker's closed short-circuit records a
|
|
951
|
+
// terminal `abandoned` audit row and drives the loop down its terminate/abandon end event.
|
|
952
|
+
// One canonical abandon implementation lives in the worker — the poller only routes to it.
|
|
953
|
+
await flipToMergingThenPublish(data, engine, prKey, "waiting_merge", {
|
|
954
|
+
name: "merge-ready",
|
|
955
|
+
correlationKey: prKey,
|
|
956
|
+
variables: { mergeState: "ready", failingChecks: 0, failingChecksList: "" },
|
|
957
|
+
});
|
|
958
|
+
console.log(`[poller] closed without merging -> ${prKey}`);
|
|
959
|
+
continue;
|
|
960
|
+
}
|
|
944
961
|
const verdict = classifyMergeability(st);
|
|
945
962
|
if (verdict === "waiting") {
|
|
946
963
|
// Frugal-CI remedy (#43): when the repo publishes a merge protocol that wants a fresh
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanobpm/nano-workforce",
|
|
3
|
-
"version": "0.101.
|
|
3
|
+
"version": "0.101.1",
|
|
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",
|
|
@@ -140,6 +140,7 @@
|
|
|
140
140
|
<bpmn:outgoing>f_m_gMerged</bpmn:outgoing>
|
|
141
141
|
<bpmn:outgoing>f_m_gQueued</bpmn:outgoing>
|
|
142
142
|
<bpmn:outgoing>f_m_gRetry</bpmn:outgoing>
|
|
143
|
+
<bpmn:outgoing>f_m_gAbandoned</bpmn:outgoing>
|
|
143
144
|
<bpmn:outgoing>f_m_gBlocked</bpmn:outgoing>
|
|
144
145
|
</bpmn:exclusiveGateway>
|
|
145
146
|
<bpmn:exclusiveGateway id="gw-merge-retry" name="retry within budget?" default="f_mr_giveup">
|
|
@@ -176,6 +177,10 @@
|
|
|
176
177
|
<bpmn:endEvent id="MergeEnd" name="Merged">
|
|
177
178
|
<bpmn:incoming>f_m_done</bpmn:incoming>
|
|
178
179
|
</bpmn:endEvent>
|
|
180
|
+
<bpmn:endEvent id="MergeAbandoned" name="Abandoned (PR closed)">
|
|
181
|
+
<bpmn:incoming>f_m_gAbandoned</bpmn:incoming>
|
|
182
|
+
<bpmn:terminateEventDefinition id="ted_merge_abandoned" />
|
|
183
|
+
</bpmn:endEvent>
|
|
179
184
|
<bpmn:serviceTask id="merge-esc-conflict" name="Escalate: not mergeable">
|
|
180
185
|
<bpmn:extensionElements>
|
|
181
186
|
<zeebe:taskDefinition type="pr.persist-escalation" />
|
|
@@ -392,6 +397,9 @@
|
|
|
392
397
|
<bpmn:sequenceFlow id="f_eg_evicted" sourceRef="eg-landed" targetRef="wait-evicted" />
|
|
393
398
|
<bpmn:sequenceFlow id="f_m_evicted" sourceRef="wait-evicted" targetRef="arm-merge" />
|
|
394
399
|
<bpmn:sequenceFlow id="f_m_gBlocked" name="blocked" sourceRef="gw-merge" targetRef="merge-esc-attempt" />
|
|
400
|
+
<bpmn:sequenceFlow id="f_m_gAbandoned" name="abandoned (PR closed)" sourceRef="gw-merge" targetRef="MergeAbandoned">
|
|
401
|
+
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">=mergeStatus = "abandoned"</bpmn:conditionExpression>
|
|
402
|
+
</bpmn:sequenceFlow>
|
|
395
403
|
<bpmn:sequenceFlow id="f_m_landed" sourceRef="wait-landed" targetRef="mark-merged" />
|
|
396
404
|
<bpmn:sequenceFlow id="f_m_done" sourceRef="mark-merged" targetRef="MergeEnd" />
|
|
397
405
|
<bpmn:sequenceFlow id="f_m_escC" sourceRef="merge-esc-conflict" targetRef="wait-merge-answer" />
|
|
@@ -440,13 +448,13 @@
|
|
|
440
448
|
<bpmndi:BPMNShape id="BPMNShape_gw-merge" bpmnElement="gw-merge" isMarkerVisible="true">
|
|
441
449
|
<dc:Bounds x="1063" y="95" width="50" height="50" />
|
|
442
450
|
<bpmndi:BPMNLabel>
|
|
443
|
-
<dc:Bounds x="
|
|
451
|
+
<dc:Bounds x="1102" y="150" width="53" height="28" />
|
|
444
452
|
</bpmndi:BPMNLabel>
|
|
445
453
|
</bpmndi:BPMNShape>
|
|
446
454
|
<bpmndi:BPMNShape id="BPMNShape_gw-merge-retry" bpmnElement="gw-merge-retry" isMarkerVisible="true">
|
|
447
|
-
<dc:Bounds x="1263" y="
|
|
455
|
+
<dc:Bounds x="1263" y="1375" width="50" height="50" />
|
|
448
456
|
<bpmndi:BPMNLabel>
|
|
449
|
-
<dc:Bounds x="1344" y="
|
|
457
|
+
<dc:Bounds x="1344" y="1410" width="88" height="28" />
|
|
450
458
|
</bpmndi:BPMNLabel>
|
|
451
459
|
</bpmndi:BPMNShape>
|
|
452
460
|
<bpmndi:BPMNShape id="BPMNShape_eg-landed" bpmnElement="eg-landed" isMarkerVisible="true">
|
|
@@ -476,37 +484,43 @@
|
|
|
476
484
|
<dc:Bounds x="1863" y="143" width="50" height="14" />
|
|
477
485
|
</bpmndi:BPMNLabel>
|
|
478
486
|
</bpmndi:BPMNShape>
|
|
487
|
+
<bpmndi:BPMNShape id="BPMNShape_MergeAbandoned" bpmnElement="MergeAbandoned">
|
|
488
|
+
<dc:Bounds x="1270" y="582" width="36" height="36" />
|
|
489
|
+
<bpmndi:BPMNLabel>
|
|
490
|
+
<dc:Bounds x="1227" y="623" width="82" height="28" />
|
|
491
|
+
</bpmndi:BPMNLabel>
|
|
492
|
+
</bpmndi:BPMNShape>
|
|
479
493
|
<bpmndi:BPMNShape id="BPMNShape_merge-esc-conflict" bpmnElement="merge-esc-conflict">
|
|
480
|
-
<dc:Bounds x="1238" y="
|
|
494
|
+
<dc:Bounds x="1238" y="1840" width="100" height="80" />
|
|
481
495
|
</bpmndi:BPMNShape>
|
|
482
496
|
<bpmndi:BPMNShape id="BPMNShape_merge-esc-attempt" bpmnElement="merge-esc-attempt">
|
|
483
|
-
<dc:Bounds x="1438" y="
|
|
497
|
+
<dc:Bounds x="1438" y="1360" width="100" height="80" />
|
|
484
498
|
</bpmndi:BPMNShape>
|
|
485
499
|
<bpmndi:BPMNShape id="BPMNShape_gw-merge-escalated" bpmnElement="gw-merge-escalated" isMarkerVisible="true">
|
|
486
|
-
<dc:Bounds x="1663" y="
|
|
500
|
+
<dc:Bounds x="1663" y="1375" width="50" height="50" />
|
|
487
501
|
<bpmndi:BPMNLabel>
|
|
488
|
-
<dc:Bounds x="1651" y="
|
|
502
|
+
<dc:Bounds x="1651" y="1342" width="74" height="28" />
|
|
489
503
|
</bpmndi:BPMNLabel>
|
|
490
504
|
</bpmndi:BPMNShape>
|
|
491
505
|
<bpmndi:BPMNShape id="BPMNShape_wait-merge-answer" bpmnElement="wait-merge-answer">
|
|
492
|
-
<dc:Bounds x="1838" y="
|
|
506
|
+
<dc:Bounds x="1838" y="1360" width="100" height="80" />
|
|
493
507
|
</bpmndi:BPMNShape>
|
|
494
508
|
<bpmndi:BPMNShape id="BPMNShape_record-merge-answer" bpmnElement="record-merge-answer">
|
|
495
|
-
<dc:Bounds x="2038" y="
|
|
509
|
+
<dc:Bounds x="2038" y="1360" width="100" height="80" />
|
|
496
510
|
</bpmndi:BPMNShape>
|
|
497
511
|
<bpmndi:BPMNShape id="BPMNShape_gw-ci-fix" bpmnElement="gw-ci-fix" isMarkerVisible="true">
|
|
498
|
-
<dc:Bounds x="863" y="
|
|
512
|
+
<dc:Bounds x="863" y="1855" width="50" height="50" />
|
|
499
513
|
<bpmndi:BPMNLabel>
|
|
500
|
-
<dc:Bounds x="769" y="
|
|
514
|
+
<dc:Bounds x="769" y="1873" width="89" height="14" />
|
|
501
515
|
</bpmndi:BPMNLabel>
|
|
502
516
|
</bpmndi:BPMNShape>
|
|
503
517
|
<bpmndi:BPMNShape id="BPMNShape_fix-ci" bpmnElement="fix-ci">
|
|
504
|
-
<dc:Bounds x="1038" y="
|
|
518
|
+
<dc:Bounds x="1038" y="720" width="100" height="80" />
|
|
505
519
|
</bpmndi:BPMNShape>
|
|
506
520
|
<bpmndi:BPMNShape id="BPMNShape_gw-ci-result" bpmnElement="gw-ci-result" isMarkerVisible="true">
|
|
507
|
-
<dc:Bounds x="1263" y="
|
|
521
|
+
<dc:Bounds x="1263" y="735" width="50" height="50" />
|
|
508
522
|
<bpmndi:BPMNLabel>
|
|
509
|
-
<dc:Bounds x="1225" y="
|
|
523
|
+
<dc:Bounds x="1225" y="790" width="46" height="14" />
|
|
510
524
|
</bpmndi:BPMNLabel>
|
|
511
525
|
</bpmndi:BPMNShape>
|
|
512
526
|
<bpmndi:BPMNShape id="BPMNShape_gw-rebase" bpmnElement="gw-rebase" isMarkerVisible="true">
|
|
@@ -516,27 +530,27 @@
|
|
|
516
530
|
</bpmndi:BPMNLabel>
|
|
517
531
|
</bpmndi:BPMNShape>
|
|
518
532
|
<bpmndi:BPMNShape id="BPMNShape_rebase" bpmnElement="rebase">
|
|
519
|
-
<dc:Bounds x="1038" y="
|
|
533
|
+
<dc:Bounds x="1038" y="1040" width="100" height="80" />
|
|
520
534
|
</bpmndi:BPMNShape>
|
|
521
535
|
<bpmndi:BPMNShape id="BPMNShape_gw-rebase-result" bpmnElement="gw-rebase-result" isMarkerVisible="true">
|
|
522
|
-
<dc:Bounds x="1263" y="
|
|
536
|
+
<dc:Bounds x="1263" y="1055" width="50" height="50" />
|
|
523
537
|
<bpmndi:BPMNLabel>
|
|
524
|
-
<dc:Bounds x="1218" y="
|
|
538
|
+
<dc:Bounds x="1218" y="1110" width="60" height="14" />
|
|
525
539
|
</bpmndi:BPMNLabel>
|
|
526
540
|
</bpmndi:BPMNShape>
|
|
527
541
|
<bpmndi:BPMNShape id="BPMNShape_record-merge-dep" bpmnElement="record-merge-dep">
|
|
528
|
-
<dc:Bounds x="1438" y="
|
|
542
|
+
<dc:Bounds x="1438" y="880" width="100" height="80" />
|
|
529
543
|
</bpmndi:BPMNShape>
|
|
530
544
|
<bpmndi:BPMNShape id="BPMNShape_be_fixci_sla" bpmnElement="be_fixci_sla">
|
|
531
|
-
<dc:Bounds x="1070" y="
|
|
545
|
+
<dc:Bounds x="1070" y="782" width="36" height="36" />
|
|
532
546
|
<bpmndi:BPMNLabel>
|
|
533
|
-
<dc:Bounds x="1046" y="
|
|
547
|
+
<dc:Bounds x="1046" y="863" width="84" height="14" />
|
|
534
548
|
</bpmndi:BPMNLabel>
|
|
535
549
|
</bpmndi:BPMNShape>
|
|
536
550
|
<bpmndi:BPMNShape id="BPMNShape_be_rebase_sla" bpmnElement="be_rebase_sla">
|
|
537
|
-
<dc:Bounds x="1070" y="
|
|
551
|
+
<dc:Bounds x="1070" y="1102" width="36" height="36" />
|
|
538
552
|
<bpmndi:BPMNLabel>
|
|
539
|
-
<dc:Bounds x="1046" y="
|
|
553
|
+
<dc:Bounds x="1046" y="1183" width="84" height="14" />
|
|
540
554
|
</bpmndi:BPMNLabel>
|
|
541
555
|
</bpmndi:BPMNShape>
|
|
542
556
|
<bpmndi:BPMNEdge id="BPMNEdge_f_m_start" bpmnElement="f_m_start">
|
|
@@ -578,47 +592,47 @@
|
|
|
578
592
|
<di:waypoint x="1870" y="120" />
|
|
579
593
|
</bpmndi:BPMNEdge>
|
|
580
594
|
<bpmndi:BPMNEdge id="BPMNEdge_f_ci_giveup" bpmnElement="f_ci_giveup">
|
|
581
|
-
<di:waypoint x="913" y="
|
|
582
|
-
<di:waypoint x="1238" y="
|
|
595
|
+
<di:waypoint x="913" y="1880" />
|
|
596
|
+
<di:waypoint x="1238" y="1880" />
|
|
583
597
|
<bpmndi:BPMNLabel>
|
|
584
|
-
<dc:Bounds x="1032" y="
|
|
598
|
+
<dc:Bounds x="1032" y="1847" width="67" height="28" />
|
|
585
599
|
</bpmndi:BPMNLabel>
|
|
586
600
|
</bpmndi:BPMNEdge>
|
|
587
601
|
<bpmndi:BPMNEdge id="BPMNEdge_f_ci_reconcile" bpmnElement="f_ci_reconcile">
|
|
588
|
-
<di:waypoint x="1288" y="
|
|
589
|
-
<di:waypoint x="1288" y="
|
|
590
|
-
<di:waypoint x="402" y="
|
|
602
|
+
<di:waypoint x="1288" y="785" />
|
|
603
|
+
<di:waypoint x="1288" y="840" />
|
|
604
|
+
<di:waypoint x="402" y="840" />
|
|
591
605
|
<di:waypoint x="402" y="160" />
|
|
592
606
|
<bpmndi:BPMNLabel>
|
|
593
|
-
<dc:Bounds x="804" y="
|
|
607
|
+
<dc:Bounds x="804" y="779" width="82" height="56" />
|
|
594
608
|
</bpmndi:BPMNLabel>
|
|
595
609
|
</bpmndi:BPMNEdge>
|
|
596
610
|
<bpmndi:BPMNEdge id="BPMNEdge_f_reb_giveup" bpmnElement="f_reb_giveup">
|
|
597
611
|
<di:waypoint x="913" y="280" />
|
|
598
612
|
<di:waypoint x="933" y="280" />
|
|
599
613
|
<di:waypoint x="933" y="305" />
|
|
600
|
-
<di:waypoint x="
|
|
601
|
-
<di:waypoint x="
|
|
602
|
-
<di:waypoint x="1288" y="
|
|
603
|
-
<di:waypoint x="1288" y="
|
|
614
|
+
<di:waypoint x="1326" y="305" />
|
|
615
|
+
<di:waypoint x="1326" y="1820" />
|
|
616
|
+
<di:waypoint x="1288" y="1820" />
|
|
617
|
+
<di:waypoint x="1288" y="1840" />
|
|
604
618
|
<bpmndi:BPMNLabel>
|
|
605
|
-
<dc:Bounds x="
|
|
619
|
+
<dc:Bounds x="1331" y="1566" width="67" height="28" />
|
|
606
620
|
</bpmndi:BPMNLabel>
|
|
607
621
|
</bpmndi:BPMNEdge>
|
|
608
622
|
<bpmndi:BPMNEdge id="BPMNEdge_f_reb_reconcile" bpmnElement="f_reb_reconcile">
|
|
609
|
-
<di:waypoint x="1288" y="
|
|
610
|
-
<di:waypoint x="1288" y="
|
|
611
|
-
<di:waypoint x="402" y="
|
|
623
|
+
<di:waypoint x="1288" y="1105" />
|
|
624
|
+
<di:waypoint x="1288" y="1160" />
|
|
625
|
+
<di:waypoint x="402" y="1160" />
|
|
612
626
|
<di:waypoint x="402" y="160" />
|
|
613
627
|
<bpmndi:BPMNLabel>
|
|
614
|
-
<dc:Bounds x="804" y="
|
|
628
|
+
<dc:Bounds x="804" y="1099" width="82" height="56" />
|
|
615
629
|
</bpmndi:BPMNLabel>
|
|
616
630
|
</bpmndi:BPMNEdge>
|
|
617
631
|
<bpmndi:BPMNEdge id="BPMNEdge_f_mr_giveup" bpmnElement="f_mr_giveup">
|
|
618
|
-
<di:waypoint x="1313" y="
|
|
619
|
-
<di:waypoint x="1438" y="
|
|
632
|
+
<di:waypoint x="1313" y="1400" />
|
|
633
|
+
<di:waypoint x="1438" y="1400" />
|
|
620
634
|
<bpmndi:BPMNLabel>
|
|
621
|
-
<dc:Bounds x="
|
|
635
|
+
<dc:Bounds x="1332" y="1367" width="67" height="28" />
|
|
622
636
|
</bpmndi:BPMNLabel>
|
|
623
637
|
</bpmndi:BPMNEdge>
|
|
624
638
|
<bpmndi:BPMNEdge id="BPMNEdge_f_eg_landed" bpmnElement="f_eg_landed">
|
|
@@ -631,44 +645,44 @@
|
|
|
631
645
|
<di:waypoint x="1688" y="160" />
|
|
632
646
|
</bpmndi:BPMNEdge>
|
|
633
647
|
<bpmndi:BPMNEdge id="BPMNEdge_f_m_escC" bpmnElement="f_m_escC">
|
|
634
|
-
<di:waypoint x="1338" y="
|
|
635
|
-
<di:waypoint x="1888" y="
|
|
636
|
-
<di:waypoint x="1888" y="
|
|
648
|
+
<di:waypoint x="1338" y="1880" />
|
|
649
|
+
<di:waypoint x="1888" y="1880" />
|
|
650
|
+
<di:waypoint x="1888" y="1440" />
|
|
637
651
|
</bpmndi:BPMNEdge>
|
|
638
652
|
<bpmndi:BPMNEdge id="BPMNEdge_f_m_escA" bpmnElement="f_m_escA">
|
|
639
|
-
<di:waypoint x="1538" y="
|
|
640
|
-
<di:waypoint x="1663" y="
|
|
653
|
+
<di:waypoint x="1538" y="1400" />
|
|
654
|
+
<di:waypoint x="1663" y="1400" />
|
|
641
655
|
</bpmndi:BPMNEdge>
|
|
642
656
|
<bpmndi:BPMNEdge id="BPMNEdge_f_m_escReenter" bpmnElement="f_m_escReenter">
|
|
643
|
-
<di:waypoint x="1688" y="
|
|
644
|
-
<di:waypoint x="1688" y="
|
|
645
|
-
<di:waypoint x="402" y="
|
|
657
|
+
<di:waypoint x="1688" y="1425" />
|
|
658
|
+
<di:waypoint x="1688" y="1460" />
|
|
659
|
+
<di:waypoint x="402" y="1460" />
|
|
646
660
|
<di:waypoint x="402" y="160" />
|
|
647
661
|
<bpmndi:BPMNLabel>
|
|
648
|
-
<dc:Bounds x="1008" y="
|
|
662
|
+
<dc:Bounds x="1008" y="1465" width="74" height="42" />
|
|
649
663
|
</bpmndi:BPMNLabel>
|
|
650
664
|
</bpmndi:BPMNEdge>
|
|
651
665
|
<bpmndi:BPMNEdge id="BPMNEdge_f_m_answerRecord" bpmnElement="f_m_answerRecord">
|
|
652
|
-
<di:waypoint x="1938" y="
|
|
653
|
-
<di:waypoint x="2038" y="
|
|
666
|
+
<di:waypoint x="1938" y="1400" />
|
|
667
|
+
<di:waypoint x="2038" y="1400" />
|
|
654
668
|
</bpmndi:BPMNEdge>
|
|
655
669
|
<bpmndi:BPMNEdge id="BPMNEdge_f_m_answer" bpmnElement="f_m_answer">
|
|
656
|
-
<di:waypoint x="2088" y="
|
|
657
|
-
<di:waypoint x="2088" y="
|
|
658
|
-
<di:waypoint x="402" y="
|
|
670
|
+
<di:waypoint x="2088" y="1440" />
|
|
671
|
+
<di:waypoint x="2088" y="1460" />
|
|
672
|
+
<di:waypoint x="402" y="1460" />
|
|
659
673
|
<di:waypoint x="402" y="160" />
|
|
660
674
|
</bpmndi:BPMNEdge>
|
|
661
675
|
<bpmndi:BPMNEdge id="BPMNEdge_f_m_mCiFix" bpmnElement="f_m_mCiFix">
|
|
662
676
|
<di:waypoint x="713" y="145" />
|
|
663
677
|
<di:waypoint x="713" y="318" />
|
|
664
|
-
<di:waypoint x="
|
|
665
|
-
<di:waypoint x="
|
|
666
|
-
<di:waypoint x="863" y="
|
|
667
|
-
<di:waypoint x="863" y="
|
|
668
|
-
<di:waypoint x="888" y="
|
|
669
|
-
<di:waypoint x="888" y="
|
|
678
|
+
<di:waypoint x="1333" y="318" />
|
|
679
|
+
<di:waypoint x="1333" y="1340" />
|
|
680
|
+
<di:waypoint x="863" y="1340" />
|
|
681
|
+
<di:waypoint x="863" y="1835" />
|
|
682
|
+
<di:waypoint x="888" y="1835" />
|
|
683
|
+
<di:waypoint x="888" y="1855" />
|
|
670
684
|
<bpmndi:BPMNLabel>
|
|
671
|
-
<dc:Bounds x="1011" y="
|
|
685
|
+
<dc:Bounds x="1011" y="1293" width="60" height="42" />
|
|
672
686
|
</bpmndi:BPMNLabel>
|
|
673
687
|
</bpmndi:BPMNEdge>
|
|
674
688
|
<bpmndi:BPMNEdge id="BPMNEdge_f_m_mRebase" bpmnElement="f_m_mRebase">
|
|
@@ -682,42 +696,42 @@
|
|
|
682
696
|
<bpmndi:BPMNEdge id="BPMNEdge_f_m_mBlocked" bpmnElement="f_m_mBlocked">
|
|
683
697
|
<di:waypoint x="713" y="145" />
|
|
684
698
|
<di:waypoint x="713" y="318" />
|
|
685
|
-
<di:waypoint x="
|
|
686
|
-
<di:waypoint x="
|
|
687
|
-
<di:waypoint x="1218" y="
|
|
688
|
-
<di:waypoint x="1218" y="
|
|
689
|
-
<di:waypoint x="1238" y="
|
|
699
|
+
<di:waypoint x="1326" y="318" />
|
|
700
|
+
<di:waypoint x="1326" y="1340" />
|
|
701
|
+
<di:waypoint x="1218" y="1340" />
|
|
702
|
+
<di:waypoint x="1218" y="1860" />
|
|
703
|
+
<di:waypoint x="1238" y="1860" />
|
|
690
704
|
<bpmndi:BPMNLabel>
|
|
691
|
-
<dc:Bounds x="
|
|
705
|
+
<dc:Bounds x="1236" y="862" width="85" height="14" />
|
|
692
706
|
</bpmndi:BPMNLabel>
|
|
693
707
|
</bpmndi:BPMNEdge>
|
|
694
708
|
<bpmndi:BPMNEdge id="BPMNEdge_f_ci_go" bpmnElement="f_ci_go">
|
|
695
|
-
<di:waypoint x="888" y="
|
|
696
|
-
<di:waypoint x="888" y="
|
|
697
|
-
<di:waypoint x="2158" y="
|
|
698
|
-
<di:waypoint x="2158" y="
|
|
699
|
-
<di:waypoint x="1018" y="
|
|
700
|
-
<di:waypoint x="1018" y="
|
|
701
|
-
<di:waypoint x="1038" y="
|
|
709
|
+
<di:waypoint x="888" y="1905" />
|
|
710
|
+
<di:waypoint x="888" y="1925" />
|
|
711
|
+
<di:waypoint x="2158" y="1925" />
|
|
712
|
+
<di:waypoint x="2158" y="715" />
|
|
713
|
+
<di:waypoint x="1018" y="715" />
|
|
714
|
+
<di:waypoint x="1018" y="740" />
|
|
715
|
+
<di:waypoint x="1038" y="740" />
|
|
702
716
|
<bpmndi:BPMNLabel>
|
|
703
|
-
<dc:Bounds x="2163" y="
|
|
717
|
+
<dc:Bounds x="2163" y="1306" width="49" height="28" />
|
|
704
718
|
</bpmndi:BPMNLabel>
|
|
705
719
|
</bpmndi:BPMNEdge>
|
|
706
720
|
<bpmndi:BPMNEdge id="BPMNEdge_f_ci_blocked" bpmnElement="f_ci_blocked">
|
|
707
|
-
<di:waypoint x="1313" y="
|
|
708
|
-
<di:waypoint x="1418" y="
|
|
709
|
-
<di:waypoint x="1418" y="
|
|
710
|
-
<di:waypoint x="1438" y="
|
|
721
|
+
<di:waypoint x="1313" y="760" />
|
|
722
|
+
<di:waypoint x="1418" y="760" />
|
|
723
|
+
<di:waypoint x="1418" y="1400" />
|
|
724
|
+
<di:waypoint x="1438" y="1400" />
|
|
711
725
|
<bpmndi:BPMNLabel>
|
|
712
|
-
<dc:Bounds x="1423" y="
|
|
726
|
+
<dc:Bounds x="1423" y="1013" width="89" height="14" />
|
|
713
727
|
</bpmndi:BPMNLabel>
|
|
714
728
|
</bpmndi:BPMNEdge>
|
|
715
729
|
<bpmndi:BPMNEdge id="BPMNEdge_f_ci_wait" bpmnElement="f_ci_wait">
|
|
716
|
-
<di:waypoint x="1313" y="
|
|
717
|
-
<di:waypoint x="1488" y="
|
|
718
|
-
<di:waypoint x="1488" y="
|
|
730
|
+
<di:waypoint x="1313" y="760" />
|
|
731
|
+
<di:waypoint x="1488" y="760" />
|
|
732
|
+
<di:waypoint x="1488" y="880" />
|
|
719
733
|
<bpmndi:BPMNLabel>
|
|
720
|
-
<dc:Bounds x="1416" y="
|
|
734
|
+
<dc:Bounds x="1416" y="727" width="75" height="28" />
|
|
721
735
|
</bpmndi:BPMNLabel>
|
|
722
736
|
</bpmndi:BPMNEdge>
|
|
723
737
|
<bpmndi:BPMNEdge id="BPMNEdge_f_reb_go" bpmnElement="f_reb_go">
|
|
@@ -725,32 +739,32 @@
|
|
|
725
739
|
<di:waypoint x="933" y="280" />
|
|
726
740
|
<di:waypoint x="933" y="305" />
|
|
727
741
|
<di:waypoint x="1733" y="305" />
|
|
728
|
-
<di:waypoint x="1733" y="
|
|
729
|
-
<di:waypoint x="
|
|
730
|
-
<di:waypoint x="
|
|
731
|
-
<di:waypoint x="
|
|
732
|
-
<di:waypoint x="
|
|
733
|
-
<di:waypoint x="1018" y="
|
|
734
|
-
<di:waypoint x="1018" y="
|
|
735
|
-
<di:waypoint x="1038" y="
|
|
742
|
+
<di:waypoint x="1733" y="1820" />
|
|
743
|
+
<di:waypoint x="1250" y="1820" />
|
|
744
|
+
<di:waypoint x="1250" y="1355" />
|
|
745
|
+
<di:waypoint x="1358" y="1355" />
|
|
746
|
+
<di:waypoint x="1358" y="1035" />
|
|
747
|
+
<di:waypoint x="1018" y="1035" />
|
|
748
|
+
<di:waypoint x="1018" y="1060" />
|
|
749
|
+
<di:waypoint x="1038" y="1060" />
|
|
736
750
|
<bpmndi:BPMNLabel>
|
|
737
|
-
<dc:Bounds x="
|
|
751
|
+
<dc:Bounds x="1255" y="1574" width="49" height="28" />
|
|
738
752
|
</bpmndi:BPMNLabel>
|
|
739
753
|
</bpmndi:BPMNEdge>
|
|
740
754
|
<bpmndi:BPMNEdge id="BPMNEdge_f_reb_blocked" bpmnElement="f_reb_blocked">
|
|
741
|
-
<di:waypoint x="1313" y="
|
|
742
|
-
<di:waypoint x="1488" y="
|
|
743
|
-
<di:waypoint x="1488" y="
|
|
755
|
+
<di:waypoint x="1313" y="1080" />
|
|
756
|
+
<di:waypoint x="1488" y="1080" />
|
|
757
|
+
<di:waypoint x="1488" y="1360" />
|
|
744
758
|
<bpmndi:BPMNLabel>
|
|
745
|
-
<dc:Bounds x="1429" y="
|
|
759
|
+
<dc:Bounds x="1429" y="1047" width="64" height="28" />
|
|
746
760
|
</bpmndi:BPMNLabel>
|
|
747
761
|
</bpmndi:BPMNEdge>
|
|
748
762
|
<bpmndi:BPMNEdge id="BPMNEdge_f_reb_wait" bpmnElement="f_reb_wait">
|
|
749
|
-
<di:waypoint x="1288" y="
|
|
750
|
-
<di:waypoint x="1288" y="
|
|
751
|
-
<di:waypoint x="1438" y="
|
|
763
|
+
<di:waypoint x="1288" y="1055" />
|
|
764
|
+
<di:waypoint x="1288" y="920" />
|
|
765
|
+
<di:waypoint x="1438" y="920" />
|
|
752
766
|
<bpmndi:BPMNLabel>
|
|
753
|
-
<dc:Bounds x="1208" y="
|
|
767
|
+
<dc:Bounds x="1208" y="994" width="75" height="28" />
|
|
754
768
|
</bpmndi:BPMNLabel>
|
|
755
769
|
</bpmndi:BPMNEdge>
|
|
756
770
|
<bpmndi:BPMNEdge id="BPMNEdge_f_m_gQueued" bpmnElement="f_m_gQueued">
|
|
@@ -758,7 +772,7 @@
|
|
|
758
772
|
<di:waypoint x="1088" y="280" />
|
|
759
773
|
<di:waypoint x="1263" y="280" />
|
|
760
774
|
<bpmndi:BPMNLabel>
|
|
761
|
-
<dc:Bounds x="1093" y="
|
|
775
|
+
<dc:Bounds x="1093" y="216" width="46" height="14" />
|
|
762
776
|
</bpmndi:BPMNLabel>
|
|
763
777
|
</bpmndi:BPMNEdge>
|
|
764
778
|
<bpmndi:BPMNEdge id="BPMNEdge_f_m_gRetry" bpmnElement="f_m_gRetry">
|
|
@@ -767,10 +781,10 @@
|
|
|
767
781
|
<di:waypoint x="1926" y="75" />
|
|
768
782
|
<di:waypoint x="1926" y="318" />
|
|
769
783
|
<di:waypoint x="1643" y="318" />
|
|
770
|
-
<di:waypoint x="1643" y="
|
|
771
|
-
<di:waypoint x="1263" y="
|
|
772
|
-
<di:waypoint x="1288" y="
|
|
773
|
-
<di:waypoint x="1288" y="
|
|
784
|
+
<di:waypoint x="1643" y="1445" />
|
|
785
|
+
<di:waypoint x="1263" y="1445" />
|
|
786
|
+
<di:waypoint x="1288" y="1445" />
|
|
787
|
+
<di:waypoint x="1288" y="1425" />
|
|
774
788
|
<bpmndi:BPMNLabel>
|
|
775
789
|
<dc:Bounds x="1746" y="285" width="78" height="28" />
|
|
776
790
|
</bpmndi:BPMNLabel>
|
|
@@ -784,80 +798,92 @@
|
|
|
784
798
|
<di:waypoint x="1088" y="95" />
|
|
785
799
|
<di:waypoint x="1088" y="75" />
|
|
786
800
|
<di:waypoint x="1926" y="75" />
|
|
787
|
-
<di:waypoint x="1926" y="
|
|
788
|
-
<di:waypoint x="1418" y="
|
|
789
|
-
<di:waypoint x="1418" y="
|
|
790
|
-
<di:waypoint x="1438" y="
|
|
801
|
+
<di:waypoint x="1926" y="980" />
|
|
802
|
+
<di:waypoint x="1418" y="980" />
|
|
803
|
+
<di:waypoint x="1418" y="1380" />
|
|
804
|
+
<di:waypoint x="1438" y="1380" />
|
|
805
|
+
<bpmndi:BPMNLabel>
|
|
806
|
+
<dc:Bounds x="1931" y="642" width="53" height="14" />
|
|
807
|
+
</bpmndi:BPMNLabel>
|
|
808
|
+
</bpmndi:BPMNEdge>
|
|
809
|
+
<bpmndi:BPMNEdge id="BPMNEdge_f_m_gAbandoned" bpmnElement="f_m_gAbandoned">
|
|
810
|
+
<di:waypoint x="1088" y="145" />
|
|
811
|
+
<di:waypoint x="1088" y="165" />
|
|
812
|
+
<di:waypoint x="818" y="165" />
|
|
813
|
+
<di:waypoint x="818" y="145" />
|
|
814
|
+
<di:waypoint x="668" y="145" />
|
|
815
|
+
<di:waypoint x="668" y="600" />
|
|
816
|
+
<di:waypoint x="1270" y="600" />
|
|
791
817
|
<bpmndi:BPMNLabel>
|
|
792
|
-
<dc:Bounds x="
|
|
818
|
+
<dc:Bounds x="722" y="150" width="82" height="28" />
|
|
793
819
|
</bpmndi:BPMNLabel>
|
|
794
820
|
</bpmndi:BPMNEdge>
|
|
795
821
|
<bpmndi:BPMNEdge id="BPMNEdge_f_ci_done" bpmnElement="f_ci_done">
|
|
796
|
-
<di:waypoint x="1138" y="
|
|
797
|
-
<di:waypoint x="1263" y="
|
|
822
|
+
<di:waypoint x="1138" y="760" />
|
|
823
|
+
<di:waypoint x="1263" y="760" />
|
|
798
824
|
</bpmndi:BPMNEdge>
|
|
799
825
|
<bpmndi:BPMNEdge id="BPMNEdge_f_reb_done" bpmnElement="f_reb_done">
|
|
800
|
-
<di:waypoint x="1138" y="
|
|
801
|
-
<di:waypoint x="1263" y="
|
|
826
|
+
<di:waypoint x="1138" y="1080" />
|
|
827
|
+
<di:waypoint x="1263" y="1080" />
|
|
802
828
|
</bpmndi:BPMNEdge>
|
|
803
829
|
<bpmndi:BPMNEdge id="BPMNEdge_f_m_escWait" bpmnElement="f_m_escWait">
|
|
804
|
-
<di:waypoint x="1713" y="
|
|
805
|
-
<di:waypoint x="1838" y="
|
|
830
|
+
<di:waypoint x="1713" y="1400" />
|
|
831
|
+
<di:waypoint x="1838" y="1400" />
|
|
806
832
|
</bpmndi:BPMNEdge>
|
|
807
833
|
<bpmndi:BPMNEdge id="BPMNEdge_f_reb_sla" bpmnElement="f_reb_sla">
|
|
808
|
-
<di:waypoint x="1088" y="
|
|
809
|
-
<di:waypoint x="1088" y="
|
|
810
|
-
<di:waypoint x="1218" y="
|
|
811
|
-
<di:waypoint x="1218" y="
|
|
812
|
-
<di:waypoint x="1238" y="
|
|
834
|
+
<di:waypoint x="1088" y="1138" />
|
|
835
|
+
<di:waypoint x="1088" y="1158" />
|
|
836
|
+
<di:waypoint x="1218" y="1158" />
|
|
837
|
+
<di:waypoint x="1218" y="1880" />
|
|
838
|
+
<di:waypoint x="1238" y="1880" />
|
|
813
839
|
<bpmndi:BPMNLabel>
|
|
814
|
-
<dc:Bounds x="1118" y="
|
|
840
|
+
<dc:Bounds x="1118" y="1125" width="70" height="28" />
|
|
815
841
|
</bpmndi:BPMNLabel>
|
|
816
842
|
</bpmndi:BPMNEdge>
|
|
817
843
|
<bpmndi:BPMNEdge id="BPMNEdge_f_ci_sla" bpmnElement="f_ci_sla">
|
|
818
|
-
<di:waypoint x="1088" y="
|
|
819
|
-
<di:waypoint x="1088" y="
|
|
820
|
-
<di:waypoint x="1018" y="
|
|
821
|
-
<di:waypoint x="1018" y="
|
|
822
|
-
<di:waypoint x="1558" y="
|
|
823
|
-
<di:waypoint x="1558" y="
|
|
824
|
-
<di:waypoint x="1526" y="
|
|
825
|
-
<di:waypoint x="1526" y="
|
|
844
|
+
<di:waypoint x="1088" y="818" />
|
|
845
|
+
<di:waypoint x="1088" y="838" />
|
|
846
|
+
<di:waypoint x="1018" y="838" />
|
|
847
|
+
<di:waypoint x="1018" y="700" />
|
|
848
|
+
<di:waypoint x="1558" y="700" />
|
|
849
|
+
<di:waypoint x="1558" y="980" />
|
|
850
|
+
<di:waypoint x="1526" y="980" />
|
|
851
|
+
<di:waypoint x="1526" y="1360" />
|
|
826
852
|
<bpmndi:BPMNLabel>
|
|
827
|
-
<dc:Bounds x="
|
|
853
|
+
<dc:Bounds x="1233" y="667" width="70" height="28" />
|
|
828
854
|
</bpmndi:BPMNLabel>
|
|
829
855
|
</bpmndi:BPMNEdge>
|
|
830
856
|
<bpmndi:BPMNEdge id="BPMNEdge_f_ci_fixed" bpmnElement="f_ci_fixed">
|
|
831
|
-
<di:waypoint x="1288" y="
|
|
832
|
-
<di:waypoint x="1288" y="
|
|
833
|
-
<di:waypoint x="402" y="
|
|
857
|
+
<di:waypoint x="1288" y="785" />
|
|
858
|
+
<di:waypoint x="1288" y="840" />
|
|
859
|
+
<di:waypoint x="402" y="840" />
|
|
834
860
|
<di:waypoint x="402" y="160" />
|
|
835
861
|
<bpmndi:BPMNLabel>
|
|
836
|
-
<dc:Bounds x="826" y="
|
|
862
|
+
<dc:Bounds x="826" y="848" width="39" height="14" />
|
|
837
863
|
</bpmndi:BPMNLabel>
|
|
838
864
|
</bpmndi:BPMNEdge>
|
|
839
865
|
<bpmndi:BPMNEdge id="BPMNEdge_f_reb_rebased" bpmnElement="f_reb_rebased">
|
|
840
|
-
<di:waypoint x="1288" y="
|
|
841
|
-
<di:waypoint x="1288" y="
|
|
842
|
-
<di:waypoint x="402" y="
|
|
866
|
+
<di:waypoint x="1288" y="1105" />
|
|
867
|
+
<di:waypoint x="1288" y="1160" />
|
|
868
|
+
<di:waypoint x="402" y="1160" />
|
|
843
869
|
<di:waypoint x="402" y="160" />
|
|
844
870
|
<bpmndi:BPMNLabel>
|
|
845
|
-
<dc:Bounds x="819" y="
|
|
871
|
+
<dc:Bounds x="819" y="1168" width="53" height="14" />
|
|
846
872
|
</bpmndi:BPMNLabel>
|
|
847
873
|
</bpmndi:BPMNEdge>
|
|
848
874
|
<bpmndi:BPMNEdge id="BPMNEdge_f_dep_rewait" bpmnElement="f_dep_rewait">
|
|
849
|
-
<di:waypoint x="1488" y="
|
|
850
|
-
<di:waypoint x="1488" y="
|
|
851
|
-
<di:waypoint x="234" y="
|
|
875
|
+
<di:waypoint x="1488" y="960" />
|
|
876
|
+
<di:waypoint x="1488" y="980" />
|
|
877
|
+
<di:waypoint x="234" y="980" />
|
|
852
878
|
<di:waypoint x="234" y="138" />
|
|
853
879
|
</bpmndi:BPMNEdge>
|
|
854
880
|
<bpmndi:BPMNEdge id="BPMNEdge_f_mr_go" bpmnElement="f_mr_go">
|
|
855
|
-
<di:waypoint x="1288" y="
|
|
856
|
-
<di:waypoint x="1288" y="
|
|
857
|
-
<di:waypoint x="402" y="
|
|
881
|
+
<di:waypoint x="1288" y="1425" />
|
|
882
|
+
<di:waypoint x="1288" y="1445" />
|
|
883
|
+
<di:waypoint x="402" y="1445" />
|
|
858
884
|
<di:waypoint x="402" y="160" />
|
|
859
885
|
<bpmndi:BPMNLabel>
|
|
860
|
-
<dc:Bounds x="808" y="
|
|
886
|
+
<dc:Bounds x="808" y="1412" width="49" height="28" />
|
|
861
887
|
</bpmndi:BPMNLabel>
|
|
862
888
|
</bpmndi:BPMNEdge>
|
|
863
889
|
<bpmndi:BPMNEdge id="BPMNEdge_f_m_evicted" bpmnElement="f_m_evicted">
|
|
@@ -245,3 +245,48 @@ test("pr.merge routes a transient base-moved race through the retry branch (no e
|
|
|
245
245
|
},
|
|
246
246
|
);
|
|
247
247
|
});
|
|
248
|
+
|
|
249
|
+
test("pr.merge abandons a closed-not-merged PR without escalating (terminal abandon, #342)", async () => {
|
|
250
|
+
// #342/#350: a PR CLOSED on GitHub without merging (e.g. superseded by a newer PR) can never
|
|
251
|
+
// land. The worker must NOT fall through to the land protocol — that returns blocked/conflict and
|
|
252
|
+
// escalates a merge no human can complete, orphaning the process on a dead PR. Instead it records
|
|
253
|
+
// a terminal `abandoned` audit row and returns `mergeStatus:"abandoned"` (the model's
|
|
254
|
+
// terminate/abandon end event), opening NO escalation. Symmetric with the already-merged
|
|
255
|
+
// short-circuit; both are decided from the same single live-state read.
|
|
256
|
+
await withGithub(
|
|
257
|
+
(url) => {
|
|
258
|
+
// Single-PR GET → PR is closed (state="closed") and NOT merged.
|
|
259
|
+
if (/\/pulls\/\d+$/.test(url))
|
|
260
|
+
return new Response(JSON.stringify({ merged: false, state: "closed", mergeable_state: "dirty" }));
|
|
261
|
+
return null; // no AGENTS.md / merge-protocol.json → DEFAULT gh-merge protocol
|
|
262
|
+
},
|
|
263
|
+
async (calls) => {
|
|
264
|
+
const { app, stores } = fakeApp();
|
|
265
|
+
const out = (await handler(
|
|
266
|
+
{ variables: { prKey: "acme/widgets#13", repo: "acme/widgets", prNumber: 13 } } as any,
|
|
267
|
+
app,
|
|
268
|
+
)) as Record<string, unknown>;
|
|
269
|
+
|
|
270
|
+
// Abandon short-circuit: loop-terminal `mergeStatus` only, NO escalation payload.
|
|
271
|
+
assertEquals(out, { mergeStatus: "abandoned" });
|
|
272
|
+
assertEquals(out.status, undefined);
|
|
273
|
+
assertEquals(out.question, undefined);
|
|
274
|
+
|
|
275
|
+
// Records exactly one terminal audit row tagged as the closed-PR abandon path.
|
|
276
|
+
assertEquals(stores.merges.length, 1);
|
|
277
|
+
assertEquals(stores.merges[0].outcome, "abandoned");
|
|
278
|
+
assertEquals(stores.merges[0].method, "pr-closed");
|
|
279
|
+
|
|
280
|
+
// Flips the PR row to the terminal `abandoned` status. This path drives a terminate end event
|
|
281
|
+
// that runs NO mark-merged/mark-abandoned worker, so the worker must set the terminal status
|
|
282
|
+
// itself — otherwise the row stays in a non-terminal in-flight status (e.g. `merging`) and is
|
|
283
|
+
// tracked/scanned forever even though the merge loop is done (#342 review).
|
|
284
|
+
const prRow = stores.pull_requests.find((r) => r.pr_key === "acme/widgets#13");
|
|
285
|
+
assertEquals(prRow?.status, "abandoned");
|
|
286
|
+
|
|
287
|
+
// Never attempts a merge PUT or posts an enqueue comment on the dead PR (only the read GET).
|
|
288
|
+
assertEquals(calls.some((c) => /\/merge$/.test(c.url)), false);
|
|
289
|
+
assertEquals(calls.some((c) => /\/comments$/.test(c.url)), false);
|
|
290
|
+
},
|
|
291
|
+
);
|
|
292
|
+
});
|
package/workers/merge/worker.ts
CHANGED
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
// shapes the escalation payload on a block.
|
|
13
13
|
import type { AppJobHandler } from "@nanobpm/urban";
|
|
14
14
|
import { matchTags, tag } from "@nanobpm/urban/effect";
|
|
15
|
-
import { abandonTokenFromUrl } from "../../app/abandon.ts";
|
|
15
|
+
import { ABANDONED_STATUS, abandonTokenFromUrl } from "../../app/abandon.ts";
|
|
16
16
|
import { checkBaseTarget, classifyBaseGuard } from "../../app/baseGuard.ts";
|
|
17
|
-
import { enqueueViaComment, fetchPrState, mergePr } from "../../app/github.ts";
|
|
17
|
+
import { classifyPrLiveness, enqueueViaComment, fetchPrState, mergePr } from "../../app/github.ts";
|
|
18
18
|
import { classifyMergeLanding, DEFAULT_MERGE_PROTOCOL, loadMergeProtocol } from "../../app/mergeProtocol.ts";
|
|
19
19
|
import { ensurePr, MERGE_ADMIN, MERGE_METHOD } from "../../app/service.ts";
|
|
20
20
|
import type { WorkerInputs } from "../../nano-generated/worker-io.d.ts";
|
|
@@ -23,7 +23,7 @@ import type { WorkerInputs } from "../../nano-generated/worker-io.d.ts";
|
|
|
23
23
|
type In = WorkerInputs["pr.merge"];
|
|
24
24
|
|
|
25
25
|
interface Out extends Record<string, unknown> {
|
|
26
|
-
mergeStatus: "merged" | "queued" | "blocked" | "retry";
|
|
26
|
+
mergeStatus: "merged" | "queued" | "blocked" | "retry" | "abandoned";
|
|
27
27
|
status?: string;
|
|
28
28
|
question?: string;
|
|
29
29
|
}
|
|
@@ -54,7 +54,8 @@ const handler: AppJobHandler<In, Out> = async (job, app) => {
|
|
|
54
54
|
// FK parent, and BEFORE the base-guard/protocol logic. Best-effort: a transport hiccup falls through
|
|
55
55
|
// to the normal path rather than blocking a genuine merge.
|
|
56
56
|
const pre = await fetchPrState(repo, prNumber, token).catch(() => null);
|
|
57
|
-
|
|
57
|
+
const liveness = classifyPrLiveness(pre);
|
|
58
|
+
if (liveness === "merged") {
|
|
58
59
|
await app.data.table("merges", "id").insert({
|
|
59
60
|
pr_key: prKey,
|
|
60
61
|
outcome: "merged",
|
|
@@ -65,6 +66,33 @@ const handler: AppJobHandler<In, Out> = async (job, app) => {
|
|
|
65
66
|
return { mergeStatus: "merged" };
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
// Closed-not-merged short-circuit (#342). A PR CLOSED on GitHub without merging (e.g. superseded
|
|
70
|
+
// by a newer PR) can never land: falling through to the land protocol would return
|
|
71
|
+
// blocked/conflict and escalate a merge no human can complete, orphaning the process on a dead
|
|
72
|
+
// PR (#350). Instead record a terminal `abandoned` audit row and drive the loop down its
|
|
73
|
+
// terminate/abandon end event — NOT `merge-esc-conflict`. This opens NO escalation and parks NO
|
|
74
|
+
// user task: a closed PR is terminal state, not a human decision. Symmetric with the merged
|
|
75
|
+
// short-circuit above and runs on the same live-state read, so one `fetchPrState` classifies both.
|
|
76
|
+
if (liveness === "closed") {
|
|
77
|
+
await app.data.table("merges", "id").insert({
|
|
78
|
+
pr_key: prKey,
|
|
79
|
+
outcome: "abandoned",
|
|
80
|
+
method: "pr-closed",
|
|
81
|
+
detail: "PR was closed on GitHub without merging (e.g. superseded) — abandoning the merge loop",
|
|
82
|
+
at: now,
|
|
83
|
+
});
|
|
84
|
+
// Terminal status write. This branch drives the model's terminate/abandon end event, which runs
|
|
85
|
+
// NO mark-merged worker (the merged path's `pr.mark-merged` is what sets `status:"merged"`). If
|
|
86
|
+
// we don't flip the row here it lingers on its in-flight status (e.g. the transient `merging`),
|
|
87
|
+
// so `activePrs`/delivery keep treating a dead PR as live. Symmetric with mark-merged's write;
|
|
88
|
+
// `ensurePr` above guarantees the row exists to update.
|
|
89
|
+
await app.data.table("pull_requests", "pr_key").update(prKey, {
|
|
90
|
+
status: ABANDONED_STATUS,
|
|
91
|
+
updated_at: now,
|
|
92
|
+
});
|
|
93
|
+
return { mergeStatus: "abandoned" };
|
|
94
|
+
}
|
|
95
|
+
|
|
68
96
|
// Dead-end-base guard (#60): never land a PR into a base branch that has itself already merged
|
|
69
97
|
// to the default branch — the merge would land into a dead branch and never reach `main`.
|
|
70
98
|
// GitHub only auto-retargets a PR when its base is *deleted* on merge; a merged-but-undeleted
|