@nanobpm/nano-workforce 0.101.0 → 0.102.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/github.test.ts +36 -1
- package/app/github.ts +35 -2
- package/app/mergesPerDay.test.ts +173 -0
- package/app/mergesPerDay.ts +150 -0
- package/app/queuedVerdict.test.ts +1 -0
- package/app/reviewReadinessGate.test.ts +15 -3
- package/app/service.ts +20 -1
- package/db/migrations/051_merges_per_day.sql +33 -0
- package/package.json +1 -1
- package/pages/_nav.json +2 -1
- package/pages/board.page.json +4 -0
- package/pages/cockpit.page.json +4 -0
- package/pages/epic-detail.page.json +4 -0
- package/pages/epic.page.json +4 -0
- package/pages/feature.page.json +4 -0
- package/pages/home.page.json +4 -0
- package/pages/lineage.page.json +4 -0
- package/pages/overview.page.json +4 -0
- package/pages/tasks.page.json +4 -0
- package/pages/velocity.page.json +92 -0
- package/resources/processes/merge-loop.bpmn +174 -148
- package/scripts/check-migrations.ts +12 -1
- package/workers/merge/worker.test.ts +45 -0
- package/workers/merge/worker.ts +32 -4
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
|