@nanobpm/nano-workforce 0.60.0 → 0.61.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.
@@ -0,0 +1,36 @@
1
+ // pr.record-blocked-ack — an operator acknowledged a blocked single-issue feature run (issue #172).
2
+ //
3
+ // A `blocked` outcome from `record-feature` is routed to the `feature-blocked` operator user task,
4
+ // which keeps the instance alive (parked at the operators' inbox) and holds the row at the
5
+ // NON-terminal `awaiting_operator` status so a re-dispatch of the same issue short-circuits instead
6
+ // of spawning an orphaned parallel run. This worker fires once the operator completes that task: it
7
+ // settles the row at the TERMINAL `blocked` status and records the operator's disposition note, so
8
+ // the same issue can be re-dispatched afterwards. Persistence goes through the record gateway
9
+ // (`app.data`), never hand-written SQL — matching the other record-* workers.
10
+ import type { AppJobHandler } from "@nanobpm/urban";
11
+ import { featureRuns } from "../../app/feature.ts";
12
+
13
+ interface In extends Record<string, unknown> {
14
+ featureKey: string;
15
+ note?: unknown;
16
+ }
17
+
18
+ const str = (v: unknown): string | undefined =>
19
+ typeof v === "string" && v.trim().length > 0 ? v.trim() : undefined;
20
+
21
+ const handler: AppJobHandler<In, Record<string, never>> = async (job, app) => {
22
+ const featureKey = job.variables.featureKey;
23
+ const note = str(job.variables.note);
24
+ const ts = new Date().toISOString();
25
+
26
+ await featureRuns(app.data).update(featureKey, {
27
+ status: "blocked",
28
+ delivery_label: note ? `operator: ${note}` : "acknowledged",
29
+ updated_at: ts,
30
+ });
31
+ app.log.info("record-blocked-ack", { featureKey, note: note ?? null });
32
+
33
+ return {};
34
+ };
35
+
36
+ export default handler;
@@ -45,15 +45,21 @@ const handler: AppJobHandler<In, Out> = async (job, app) => {
45
45
  // (the run is complete), just with no `pr_key` to converge.
46
46
  const prKey = parsed?.prKey ?? null;
47
47
  const featureStatus: FeatureRunStatus = status;
48
+ // A `blocked` outcome is routed (via the `blocked?` gateway) to the `feature-blocked` operator user
49
+ // task, which keeps the instance ALIVE until a human acknowledges it. Persist the row as the
50
+ // NON-terminal `awaiting_operator` for that parked window so a re-dispatch of the same issue
51
+ // short-circuits (no orphaned parallel run); `record-blocked-ack` writes the terminal `blocked`
52
+ // once the operator completes the task. `opened`/`skipped` are already terminal — persist as-is.
53
+ const rowStatus: FeatureRunStatus = status === "blocked" ? "awaiting_operator" : status;
48
54
  const ts = new Date().toISOString();
49
55
 
50
56
  await featureRuns(app.data).update(featureKey, {
51
- status: featureStatus,
57
+ status: rowStatus,
52
58
  pr_key: prKey,
53
59
  outcome: summary ?? null,
54
60
  updated_at: ts,
55
61
  });
56
- app.log.info("record-feature", { featureKey, featureStatus, prKey });
62
+ app.log.info("record-feature", { featureKey, featureStatus, rowStatus, prKey });
57
63
 
58
64
  return { featureStatus, prKey };
59
65
  };