@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.
@@ -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;