@nanobpm/nano-workforce 0.163.3 → 0.165.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 +12 -0
- package/app/contracts.ts +15 -0
- package/app/mergeLoopBehaviour.test.ts +111 -0
- package/app/mergeableWait.test.ts +39 -0
- package/app/mergeableWait.ts +38 -0
- package/app/service.ts +26 -0
- package/nano.app.json +4 -0
- package/package.json +1 -1
- package/pages/overview.page.json +11 -0
- package/resources/processes/merge-loop.bpmn +339 -217
- package/workers/merge-stall-probe/worker.ts +66 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// pr.merge-stall-probe — the mergeable-wait timer fired: the in-process poller never published
|
|
2
|
+
// `merge-ready` within `mergeableWaitTimeout` (it died across a redeploy, errored on this PR, or
|
|
3
|
+
// skipped the verdict), so the instance would otherwise sit wedged at `waiting_merge` forever with
|
|
4
|
+
// no timeout and no escalation (issue #636). This is the bounded-wait backstop: re-derive
|
|
5
|
+
// ground-truth mergeability directly from GitHub — reusing the poller's own classifier
|
|
6
|
+
// (`classifyMergeability` over a fresh `fetchPrState`, protocol-aware via `loadMergeProtocol`) — and
|
|
7
|
+
// emit `mergeState` so the *existing* `gw-mergeable` routes the token to the correct arm:
|
|
8
|
+
// • ready → attempt-merge
|
|
9
|
+
// • conflict → rebase arm
|
|
10
|
+
// • blocked → CI-fix arm
|
|
11
|
+
// • draft / waiting (still computing) → not-landable escalation (gw-mergeable default)
|
|
12
|
+
// The timer only guarantees the remediation machinery is ENTERED when the poller is dead; every
|
|
13
|
+
// downstream arm is the same one the live poller feeds. A bounded `mergeStallRounds` counter
|
|
14
|
+
// (incremented by this task's output mapping, capped by `mergeStallMax`) stops the probe from
|
|
15
|
+
// re-arming forever: once exhausted, `gw-merge-stall` routes to human escalation instead.
|
|
16
|
+
import type { AppJobHandler } from "@nanobpm/urban";
|
|
17
|
+
import { classifyMergeability, fetchPrState } from "../../app/github.ts";
|
|
18
|
+
import { loadMergeProtocol } from "../../app/mergeProtocol.ts";
|
|
19
|
+
import type { WorkerInputs } from "../../nano-generated/worker-io.d.ts";
|
|
20
|
+
|
|
21
|
+
// Input typed off the model data envelope (`MergeStallProbeIn` in merge-loop.bpmn) — ADR 0040.
|
|
22
|
+
type In = WorkerInputs["pr.merge-stall-probe"];
|
|
23
|
+
|
|
24
|
+
interface Out extends Record<string, unknown> {
|
|
25
|
+
// Mirrors the poller's `merge-ready {mergeState}` payload so the existing `gw-mergeable` FEEL
|
|
26
|
+
// routes it. `waiting` (GitHub still computing / no verdict) is surfaced verbatim; gw-mergeable
|
|
27
|
+
// has no `waiting` arm, so it falls to the not-landable default and escalates — the correct
|
|
28
|
+
// backstop when the poller is dead and 30 minutes on GitHub still cannot settle the PR.
|
|
29
|
+
mergeState: string;
|
|
30
|
+
failingChecks: number;
|
|
31
|
+
failingChecksList: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const handler: AppJobHandler<In, Out> = async (job, app) => {
|
|
35
|
+
const { repo, prNumber } = job.variables;
|
|
36
|
+
const token = process.env.GITHUB_TOKEN ?? "";
|
|
37
|
+
|
|
38
|
+
// Re-derive ground truth exactly as the poller does (service.ts block 2). Load the repo's merge
|
|
39
|
+
// protocol so the protocol-aware backstop (#392) gates a red DECLARED-required check even when
|
|
40
|
+
// GitHub reports UNSTABLE. Any transport hiccup is treated as "still waiting" — never a spurious
|
|
41
|
+
// ready/conflict verdict — so a bad read is surfaced as `waiting`, which `gw-mergeable` routes to
|
|
42
|
+
// its not-landable default (human escalation), erring toward a human rather than misrouting the
|
|
43
|
+
// token to a wrong remediation arm.
|
|
44
|
+
const st = await fetchPrState(repo, prNumber, token).catch(() => null);
|
|
45
|
+
if (st === null) {
|
|
46
|
+
return { mergeState: "waiting", failingChecks: 0, failingChecksList: "" };
|
|
47
|
+
}
|
|
48
|
+
const protocol = await loadMergeProtocol(repo, token).catch(() => null);
|
|
49
|
+
const mergeState = classifyMergeability(st, protocol ?? undefined);
|
|
50
|
+
|
|
51
|
+
app.log.info("merge-stall-probe: poller stalled — re-derived mergeability", {
|
|
52
|
+
prKey: job.variables.prKey,
|
|
53
|
+
mergeState,
|
|
54
|
+
mergeStateStatus: st.mergeStateStatus,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
mergeState,
|
|
59
|
+
// Carried for the CI-fix arm (mergeState "blocked" = a failed required check), mirroring the
|
|
60
|
+
// poller's `merge-ready` payload so fix-ci knows which gates to green.
|
|
61
|
+
failingChecks: st.failingChecks,
|
|
62
|
+
failingChecksList: st.failingCheckNames.join("\n"),
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export default handler;
|