@nanobpm/nano-workforce 0.63.0 → 0.64.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 CHANGED
@@ -1,3 +1,10 @@
1
+ # [0.64.0](https://github.com/nanobpm/nano-workforce/compare/v0.63.0...v0.64.0) (2026-08-13)
2
+
3
+
4
+ ### Features
5
+
6
+ * type workers off the generated data envelope ([#201](https://github.com/nanobpm/nano-workforce/issues/201)) ([#208](https://github.com/nanobpm/nano-workforce/issues/208)) ([792083f](https://github.com/nanobpm/nano-workforce/commit/792083f153050862a11224288b0e27e769ab06ba)), closes [nano-ide#225](https://github.com/nano-ide/issues/225) [#225](https://github.com/nanobpm/nano-workforce/issues/225) [#228](https://github.com/nanobpm/nano-workforce/issues/228) [#211](https://github.com/nanobpm/nano-workforce/issues/211) [#211](https://github.com/nanobpm/nano-workforce/issues/211)
7
+
1
8
  # [0.63.0](https://github.com/nanobpm/nano-workforce/compare/v0.62.0...v0.63.0) (2026-08-13)
2
9
 
3
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanobpm/nano-workforce",
3
- "version": "0.63.0",
3
+ "version": "0.64.0",
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",
@@ -51,7 +51,7 @@
51
51
  },
52
52
  "dependencies": {
53
53
  "@nanobpm/agentic": "^0.1.0",
54
- "@nanobpm/urban": "^0.47.0"
54
+ "@nanobpm/urban": "^0.48.0"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@biomejs/biome": "^2.4.11",
@@ -28,6 +28,10 @@
28
28
  <nano:extend name="round" type="integer" />
29
29
  <nano:extend name="status" type="string" optional="true" />
30
30
  <nano:extend name="summary" type="string" optional="true" />
31
+ <nano:extend name="repo" type="string" optional="true" />
32
+ <nano:extend name="prNumber" type="integer" optional="true" />
33
+ <nano:extend name="prUrl" type="string" optional="true" />
34
+ <nano:extend name="abandonUrl" type="string" optional="true" />
31
35
  </nano:shape>
32
36
  <nano:shape id="EscalationIn" name="Record escalation — input">
33
37
  <nano:extend name="prKey" type="string" />
@@ -36,6 +40,10 @@
36
40
  <nano:extend name="summary" type="string" optional="true" />
37
41
  <nano:extend name="question" type="string" optional="true" />
38
42
  <nano:extend name="recordRound" type="boolean" optional="true" />
43
+ <nano:extend name="repo" type="string" optional="true" />
44
+ <nano:extend name="prNumber" type="integer" optional="true" />
45
+ <nano:extend name="prUrl" type="string" optional="true" />
46
+ <nano:extend name="abandonUrl" type="string" optional="true" />
39
47
  </nano:shape>
40
48
  <nano:shape id="EscalationOut" name="Record escalation — result">
41
49
  <nano:extend name="escalationId" type="integer" optional="true" />
@@ -53,6 +61,8 @@
53
61
  <nano:extend name="prUrl" type="string" />
54
62
  <nano:extend name="round" type="integer" />
55
63
  <nano:extend name="summary" type="string" optional="true" />
64
+ <nano:extend name="convergeOnly" type="boolean" optional="true" />
65
+ <nano:extend name="abandonUrl" type="string" optional="true" />
56
66
  </nano:shape>
57
67
  <nano:shape id="ReviewReady" name="review-ready message payload">
58
68
  <nano:extend name="reviewId" type="integer" />
@@ -1,12 +1,30 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
- <bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:zeebe="http://camunda.org/schema/zeebe/1.0" id="Definitions_nano_workforce_feature" targetNamespace="http://nanobpm.io/nano-workforce">
2
+ <bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:zeebe="http://camunda.org/schema/zeebe/1.0" xmlns:nano="https://nanobpm.io/schema/shapes/1.0" id="Definitions_nano_workforce_feature" targetNamespace="http://nanobpm.io/nano-workforce">
3
3
  <bpmn:process id="feature" name="Single-issue feature run" isExecutable="true">
4
+ <bpmn:extensionElements>
5
+ <nano:shapes>
6
+ <nano:shape id="RecordFeatureIn" name="Record feature result — input">
7
+ <nano:extend name="featureKey" type="string" />
8
+ <nano:extend name="status" type="string" optional="true" />
9
+ <nano:extend name="pr" type="string" optional="true" />
10
+ <nano:extend name="summary" type="string" optional="true" />
11
+ </nano:shape>
12
+ <nano:shape id="ConvergeFeatureIn" name="Converge feature PR — input">
13
+ <nano:extend name="featureKey" type="string" />
14
+ <nano:extend name="prKey" type="string" optional="true" />
15
+ <nano:extend name="autoMerge" type="boolean" optional="true" />
16
+ </nano:shape>
17
+ </nano:shapes>
18
+ </bpmn:extensionElements>
4
19
  <bpmn:startEvent id="Start" name="Issue submitted">
5
20
  <bpmn:outgoing>f_toEnsureBase</bpmn:outgoing>
6
21
  </bpmn:startEvent>
7
22
  <bpmn:serviceTask id="ensure-base-branch" name="Ensure base branch">
8
23
  <bpmn:extensionElements>
9
24
  <zeebe:taskDefinition type="pr.ensure-base-branch" />
25
+ <zeebe:properties>
26
+ <zeebe:property name="io.nanobpm.dataEnvelope.in" value="EnsureBaseBranchIn" />
27
+ </zeebe:properties>
10
28
  <zeebe:ioMapping>
11
29
  <zeebe:input source="=repo" target="repo" />
12
30
  <zeebe:input source="=baseBranch" target="baseBranch" />
@@ -59,6 +77,9 @@
59
77
  <bpmn:serviceTask id="record-feature" name="Record result">
60
78
  <bpmn:extensionElements>
61
79
  <zeebe:taskDefinition type="pr.record-feature" />
80
+ <zeebe:properties>
81
+ <zeebe:property name="io.nanobpm.dataEnvelope.in" value="RecordFeatureIn" />
82
+ </zeebe:properties>
62
83
  </bpmn:extensionElements>
63
84
  <bpmn:incoming>w_done</bpmn:incoming>
64
85
  <bpmn:incoming>w_abandon</bpmn:incoming>
@@ -98,6 +119,9 @@
98
119
  <bpmn:serviceTask id="converge" name="Hand off to convergence loop">
99
120
  <bpmn:extensionElements>
100
121
  <zeebe:taskDefinition type="pr.converge-feature" />
122
+ <zeebe:properties>
123
+ <zeebe:property name="io.nanobpm.dataEnvelope.in" value="ConvergeFeatureIn" />
124
+ </zeebe:properties>
101
125
  </bpmn:extensionElements>
102
126
  <bpmn:incoming>f_converge</bpmn:incoming>
103
127
  <bpmn:outgoing>f_toEndConverged</bpmn:outgoing>
@@ -41,6 +41,8 @@
41
41
  <nano:extend name="prKey" type="string" />
42
42
  <nano:extend name="repo" type="string" />
43
43
  <nano:extend name="prNumber" type="integer" />
44
+ <nano:extend name="round" type="integer" optional="true" />
45
+ <nano:extend name="abandonUrl" type="string" optional="true" />
44
46
  </nano:shape>
45
47
  <nano:shape id="MergeAttemptOut" name="Attempt merge — result">
46
48
  <nano:extend name="mergeStatus" type="string" />
@@ -1,17 +1,46 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
- <bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:zeebe="http://camunda.org/schema/zeebe/1.0" id="Definitions_nano_workforce_plan" targetNamespace="http://nanobpm.io/nano-workforce">
2
+ <bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:zeebe="http://camunda.org/schema/zeebe/1.0" xmlns:nano="https://nanobpm.io/schema/shapes/1.0" id="Definitions_nano_workforce_plan" targetNamespace="http://nanobpm.io/nano-workforce">
3
3
  <bpmn:message id="Message_waveMerged" name="wave-merged">
4
4
  <bpmn:extensionElements>
5
5
  <zeebe:subscription correlationKey="=planKey" />
6
6
  </bpmn:extensionElements>
7
7
  </bpmn:message>
8
8
  <bpmn:process id="plan-fanout" name="Agent Fleet — plan &#38; implement" isExecutable="true">
9
+ <bpmn:extensionElements>
10
+ <nano:shapes>
11
+ <nano:shape id="EnsureBaseBranchIn" name="Ensure base branch — input">
12
+ <nano:extend name="repo" type="string" />
13
+ <nano:extend name="baseBranch" type="string" />
14
+ </nano:shape>
15
+ <nano:shape id="RecordPlanReviewIn" name="Record plan review — input">
16
+ <nano:extend name="planKey" type="string" />
17
+ <nano:extend name="approved" type="boolean" optional="true" />
18
+ <nano:extend name="findings" type="string" optional="true" />
19
+ <nano:extend name="planReviewEpoch" type="integer" optional="true" />
20
+ </nano:shape>
21
+ <nano:shape id="SelectWaveIn" name="Select wave — input">
22
+ <nano:extend name="planKey" type="string" />
23
+ <nano:extend name="currentWave" type="integer" />
24
+ </nano:shape>
25
+ <nano:shape id="ResolveTrialAttentionIn" name="Clear trial attention — input">
26
+ <nano:extend name="planKey" type="string" />
27
+ <nano:extend name="currentWave" type="integer" optional="true" />
28
+ <nano:extend name="trialMergeWave" type="integer" optional="true" />
29
+ </nano:shape>
30
+ <nano:shape id="RecordResultsIn" name="Finalize plan — input">
31
+ <nano:extend name="planKey" type="string" />
32
+ </nano:shape>
33
+ </nano:shapes>
34
+ </bpmn:extensionElements>
9
35
  <bpmn:startEvent id="Start" name="Issue submitted">
10
36
  <bpmn:outgoing>f_toEnsureBase</bpmn:outgoing>
11
37
  </bpmn:startEvent>
12
38
  <bpmn:serviceTask id="ensure-base-branch" name="Ensure base branch">
13
39
  <bpmn:extensionElements>
14
40
  <zeebe:taskDefinition type="pr.ensure-base-branch" />
41
+ <zeebe:properties>
42
+ <zeebe:property name="io.nanobpm.dataEnvelope.in" value="EnsureBaseBranchIn" />
43
+ </zeebe:properties>
15
44
  <zeebe:ioMapping>
16
45
  <zeebe:input source="=repo" target="repo" />
17
46
  <zeebe:input source="=baseBranch" target="baseBranch" />
@@ -56,6 +85,9 @@
56
85
  <bpmn:serviceTask id="record-plan-review" name="Record review &#38; decide">
57
86
  <bpmn:extensionElements>
58
87
  <zeebe:taskDefinition type="pr.record-plan-review" />
88
+ <zeebe:properties>
89
+ <zeebe:property name="io.nanobpm.dataEnvelope.in" value="RecordPlanReviewIn" />
90
+ </zeebe:properties>
59
91
  </bpmn:extensionElements>
60
92
  <bpmn:incoming>f_toRecordPlanReview</bpmn:incoming>
61
93
  <bpmn:outgoing>f_toGwPlanReview</bpmn:outgoing>
@@ -93,6 +125,9 @@
93
125
  <bpmn:serviceTask id="select-wave" name="Select wave">
94
126
  <bpmn:extensionElements>
95
127
  <zeebe:taskDefinition type="pr.select-wave" />
128
+ <zeebe:properties>
129
+ <zeebe:property name="io.nanobpm.dataEnvelope.in" value="SelectWaveIn" />
130
+ </zeebe:properties>
96
131
  </bpmn:extensionElements>
97
132
  <bpmn:incoming>f_plan_proceed</bpmn:incoming>
98
133
  <bpmn:incoming>f_plan_answer_proceed</bpmn:incoming>
@@ -238,6 +273,9 @@
238
273
  <bpmn:serviceTask id="resolve-trial-attention" name="Clear trial attention">
239
274
  <bpmn:extensionElements>
240
275
  <zeebe:taskDefinition type="pr.resolve-trial-attention" />
276
+ <zeebe:properties>
277
+ <zeebe:property name="io.nanobpm.dataEnvelope.in" value="ResolveTrialAttentionIn" />
278
+ </zeebe:properties>
241
279
  </bpmn:extensionElements>
242
280
  <bpmn:incoming>f_toGwTrialAnswer</bpmn:incoming>
243
281
  <bpmn:outgoing>f_resolveToTrialAnswer</bpmn:outgoing>
@@ -263,6 +301,9 @@
263
301
  <bpmn:serviceTask id="record-results" name="Finalize plan">
264
302
  <bpmn:extensionElements>
265
303
  <zeebe:taskDefinition type="pr.record-results" />
304
+ <zeebe:properties>
305
+ <zeebe:property name="io.nanobpm.dataEnvelope.in" value="RecordResultsIn" />
306
+ </zeebe:properties>
266
307
  </bpmn:extensionElements>
267
308
  <bpmn:incoming>f_done</bpmn:incoming>
268
309
  <bpmn:incoming>f_trialAnswerAbandon</bpmn:incoming>
@@ -1,12 +1,29 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
2
  <bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:zeebe="http://camunda.org/schema/zeebe/1.0" xmlns:nano="https://nanobpm.io/schema/shapes/1.0" id="Definitions_nano_workforce_retro" targetNamespace="http://nanobpm.io/nano-workforce">
3
3
  <bpmn:process id="retro" name="Epic Retrospective" isExecutable="true">
4
+ <bpmn:extensionElements>
5
+ <nano:shapes>
6
+ <nano:shape id="RetroGatherIn" name="Gather learnings — input">
7
+ <nano:extend name="planKey" type="string" />
8
+ </nano:shape>
9
+ <nano:shape id="RetroRecordIn" name="Record retro — input">
10
+ <nano:extend name="planKey" type="string" />
11
+ <nano:extend name="retroLearnings" type="integer" optional="true" />
12
+ <nano:extend name="status" type="string" optional="true" />
13
+ <nano:extend name="pr" type="string" optional="true" />
14
+ <nano:extend name="summary" type="string" optional="true" />
15
+ </nano:shape>
16
+ </nano:shapes>
17
+ </bpmn:extensionElements>
4
18
  <bpmn:startEvent id="Start" name="Epic complete">
5
19
  <bpmn:outgoing>f_start</bpmn:outgoing>
6
20
  </bpmn:startEvent>
7
21
  <bpmn:serviceTask id="gather" name="Gather learnings">
8
22
  <bpmn:extensionElements>
9
23
  <zeebe:taskDefinition type="pr.retro-gather" />
24
+ <zeebe:properties>
25
+ <zeebe:property name="io.nanobpm.dataEnvelope.in" value="RetroGatherIn" />
26
+ </zeebe:properties>
10
27
  </bpmn:extensionElements>
11
28
  <bpmn:incoming>f_start</bpmn:incoming>
12
29
  <bpmn:outgoing>f_toSynthesize</bpmn:outgoing>
@@ -27,6 +44,9 @@
27
44
  <bpmn:serviceTask id="record" name="Record retro">
28
45
  <bpmn:extensionElements>
29
46
  <zeebe:taskDefinition type="pr.retro-record" />
47
+ <zeebe:properties>
48
+ <zeebe:property name="io.nanobpm.dataEnvelope.in" value="RetroRecordIn" />
49
+ </zeebe:properties>
30
50
  </bpmn:extensionElements>
31
51
  <bpmn:incoming>f_toRecord</bpmn:incoming>
32
52
  <bpmn:outgoing>f_toEnd</bpmn:outgoing>
@@ -17,6 +17,7 @@
17
17
  // durable rows; it returns no variables, leaving the submitted `answer` untouched so it flows on to
18
18
  // the next review round.
19
19
  import type { AppJobHandler } from "@nanobpm/urban";
20
+ import type { WorkerInputs } from "../../nano-generated/worker-io.d.ts";
20
21
 
21
22
  interface Escalation extends Record<string, unknown> {
22
23
  id: number;
@@ -34,11 +35,8 @@ interface PullRequest extends Record<string, unknown> {
34
35
  updated_at: string;
35
36
  }
36
37
 
37
- interface In extends Record<string, unknown> {
38
- prKey: string;
39
- answer?: string;
40
- }
41
-
38
+ // Input typed off the model data envelope (`PrAnswerEscalationIn` in convergence-loop.bpmn) — ADR 0040.
39
+ type In = WorkerInputs["pr.answer-escalation"];
42
40
  // A string variable, or undefined when it is absent/blank. The form marks `answer` required, so a
43
41
  // blank here would be an out-of-band completion; record it as NULL rather than an empty string.
44
42
  function nonBlank(v: unknown): string | undefined {
@@ -2,10 +2,10 @@
2
2
  // evaluates its mergeability and correlates `merge-ready`. Reached both on entry to the merge
3
3
  // stage (after dependencies clear) and after a human answers a merge escalation (re-check).
4
4
  import type { AppJobHandler } from "@nanobpm/urban";
5
+ import type { WorkerInputs } from "../../nano-generated/worker-io.d.ts";
5
6
 
6
- interface In extends Record<string, unknown> {
7
- prKey: string;
8
- }
7
+ // Input typed off the model data envelope (`MergeArmIn` in merge-loop.bpmn) — ADR 0040.
8
+ type In = WorkerInputs["pr.arm-merge"];
9
9
 
10
10
  const handler: AppJobHandler<In> = async (job, app) => {
11
11
  await app.data.table("pull_requests", "pr_key").update(job.variables.prKey, {
@@ -13,12 +13,10 @@
13
13
  import type { AppJobHandler } from "@nanobpm/urban";
14
14
  import { featureRuns } from "../../app/feature.ts";
15
15
  import { MAX_ROUNDS, parsePr, submitPr } from "../../app/service.ts";
16
+ import type { WorkerInputs } from "../../nano-generated/worker-io.d.ts";
16
17
 
17
- interface In extends Record<string, unknown> {
18
- featureKey: string;
19
- prKey?: unknown;
20
- autoMerge?: unknown;
21
- }
18
+ // Input typed off the model data envelope (`ConvergeFeatureIn` in feature.bpmn) — ADR 0040.
19
+ type In = WorkerInputs["pr.converge-feature"];
22
20
 
23
21
  const handler: AppJobHandler<In, Record<string, never>> = async (job, app) => {
24
22
  const featureKey = job.variables.featureKey;
@@ -10,11 +10,10 @@
10
10
  // wrong-rooted branch).
11
11
  import type { AppJobHandler } from "@nanobpm/urban";
12
12
  import { type EnsureBaseBranchResult, ensureBaseBranch } from "../../app/github.ts";
13
+ import type { WorkerInputs } from "../../nano-generated/worker-io.d.ts";
13
14
 
14
- interface In extends Record<string, unknown> {
15
- repo: string;
16
- baseBranch: string;
17
- }
15
+ // Input typed off the model data envelope (`EnsureBaseBranchIn` in plan-fanout.bpmn) — ADR 0040.
16
+ type In = WorkerInputs["pr.ensure-base-branch"];
18
17
  interface Out extends Record<string, unknown> {
19
18
  baseBranchResult: EnsureBaseBranchResult;
20
19
  }
@@ -5,23 +5,11 @@ import type { AppJobHandler } from "@nanobpm/urban";
5
5
  import { abandonTokenFromUrl } from "../../app/abandon.ts";
6
6
  import { maybeStartRetro } from "../../app/retro.ts";
7
7
  import { AUTO_MERGE, ensurePr, startMerge } from "../../app/service.ts";
8
+ import type { WorkerInputs } from "../../nano-generated/worker-io.d.ts";
8
9
 
9
- // Extends Record so the declared fields are typed while the job may still carry
10
- // other process variables (e.g. io.nanobpm.agentResult, read by transcriptOf).
11
- interface In extends Record<string, unknown> {
12
- prKey: string;
13
- repo: string;
14
- prNumber: number;
15
- prUrl: string;
16
- round: number;
17
- summary?: string;
18
- // Per-request review-only override: when true, stop at `converged` and never hand off to the
19
- // merge-loop even if auto-merge is on globally. Set at submit time, carried on the instance.
20
- convergeOnly?: boolean;
21
- // The per-PR abandon capability URL the agent was handed; its token is preserved on a heal so
22
- // the agent's cooperative-abort check keeps resolving (see ensurePr).
23
- abandonUrl?: string;
24
- }
10
+ // Input typed off the model data envelope (`PrFinalizeIn` in convergence-loop.bpmn) ADR 0040.
11
+ // Framework-injected `io.nanobpm.agentResult` is read through the `Record`-typed `transcriptOf`.
12
+ type In = WorkerInputs["pr.finalize"];
25
13
 
26
14
  const AGENT_RESULT_KEY = "io.nanobpm.agentResult";
27
15
  function transcriptOf(vars: Record<string, unknown>): string | null {
@@ -2,10 +2,10 @@
2
2
  // `merged` state; the merge audit trail is written by pr.merge, so this only closes the row out.
3
3
  import type { AppJobHandler } from "@nanobpm/urban";
4
4
  import { maybeStartRetro } from "../../app/retro.ts";
5
+ import type { WorkerInputs } from "../../nano-generated/worker-io.d.ts";
5
6
 
6
- interface In extends Record<string, unknown> {
7
- prKey: string;
8
- }
7
+ // Input typed off the model data envelope (`MarkMergedIn` in merge-loop.bpmn) — ADR 0040.
8
+ type In = WorkerInputs["pr.mark-merged"];
9
9
 
10
10
  const handler: AppJobHandler<In> = async (job, app) => {
11
11
  const now = new Date().toISOString();
@@ -15,18 +15,10 @@ import { checkBaseTarget, classifyBaseGuard } from "../../app/baseGuard.ts";
15
15
  import { enqueueViaComment, fetchPrState, mergePr } from "../../app/github.ts";
16
16
  import { classifyMergeLanding, DEFAULT_MERGE_PROTOCOL, loadMergeProtocol } from "../../app/mergeProtocol.ts";
17
17
  import { ensurePr, MERGE_ADMIN, MERGE_METHOD } from "../../app/service.ts";
18
+ import type { WorkerInputs } from "../../nano-generated/worker-io.d.ts";
18
19
 
19
- interface In extends Record<string, unknown> {
20
- prKey: string;
21
- repo: string;
22
- prNumber: number;
23
- // Carried by the merge-loop instance (startMerge sets it to the converged round); passed to
24
- // the heal so a reconstructed row reflects the real round, not a 1-based default.
25
- round?: number;
26
- // The per-PR abandon capability URL the agent was handed; its token is preserved on a heal so
27
- // the agent's cooperative-abort check keeps resolving (see ensurePr).
28
- abandonUrl?: string;
29
- }
20
+ // Input typed off the model data envelope (`MergeAttemptIn` in merge-loop.bpmn) — ADR 0040.
21
+ type In = WorkerInputs["pr.merge"];
30
22
 
31
23
  interface Out extends Record<string, unknown> {
32
24
  mergeStatus: "merged" | "queued" | "blocked";
@@ -17,29 +17,12 @@ import type { AppJobHandler } from "@nanobpm/urban";
17
17
  import { abandonTokenFromUrl } from "../../app/abandon.ts";
18
18
  import { classifyEscalation } from "../../app/escalationTaxonomy.ts";
19
19
  import { ensurePr, parsePr } from "../../app/service.ts";
20
+ import type { WorkerInputs } from "../../nano-generated/worker-io.d.ts";
20
21
 
21
- // Extends Record so the declared fields are typed while the job may still carry
22
- // other process variables (e.g. io.nanobpm.agentResult, read by transcriptOf).
23
- interface In extends Record<string, unknown> {
24
- prKey: string;
25
- round: number;
26
- status?: string;
27
- summary?: string;
28
- question?: string;
29
- // Carried by the convergence-loop instance so a missing `pull_requests` parent can be
30
- // reconstructed before the FK-child `rounds`/`escalations` inserts.
31
- repo?: string;
32
- prNumber?: number;
33
- prUrl?: string;
34
- // The per-PR abandon capability URL the agent was handed; its token is preserved on a heal so
35
- // the agent's cooperative-abort check keeps resolving (see ensurePr).
36
- abandonUrl?: string;
37
- // False on the "review stalled" arm: `persist-round` already recorded this `round` as
38
- // `addressed`, so this escalation must not insert a second `rounds` row for the same
39
- // `pr_key`/`round_no` (which would record one round as both addressed and blocked). Absent
40
- // on the agent-raised / max-rounds arms, where no prior round row exists — so it defaults on.
41
- recordRound?: boolean;
42
- }
22
+ // Input typed off the model data envelope (`EscalationIn` in convergence-loop.bpmn) ADR 0040.
23
+ // Framework-injected variables (`io.nanobpm.agentResult`, `agent`) are read through the
24
+ // `Record`-typed helpers below rather than the envelope.
25
+ type In = WorkerInputs["pr.persist-escalation"];
43
26
 
44
27
  // A string variable, or undefined when it is absent, empty, or whitespace-only.
45
28
  // The write boundary owns *type* defaults (undefined -> column DEFAULT/NULL); this
@@ -7,23 +7,13 @@
7
7
  import type { AppJobHandler } from "@nanobpm/urban";
8
8
  import { abandonTokenFromUrl } from "../../app/abandon.ts";
9
9
  import { ensurePr, parsePr } from "../../app/service.ts";
10
+ import type { WorkerInputs } from "../../nano-generated/worker-io.d.ts";
10
11
 
11
- // Extends Record so the declared fields are typed while the job may still carry
12
- // other process variables (e.g. io.nanobpm.agentResult, read by transcriptOf).
13
- interface In extends Record<string, unknown> {
14
- prKey: string;
15
- round: number;
16
- status?: string;
17
- summary?: string;
18
- // Carried by the convergence-loop instance (set at createInstance) so a missing
19
- // `pull_requests` parent can be reconstructed before the FK-child `rounds` insert.
20
- repo?: string;
21
- prNumber?: number;
22
- prUrl?: string;
23
- // The per-PR abandon capability URL the review agent was handed; its token is preserved on a
24
- // heal so the agent's cooperative-abort check keeps resolving (see ensurePr).
25
- abandonUrl?: string;
26
- }
12
+ // Input is typed off the model data envelope (`PrPersistRoundIn` in convergence-loop.bpmn),
13
+ // the single source of truth for this worker's wire contract (ADR 0040). Framework-injected
14
+ // variables the handler still reads (`io.nanobpm.agentResult`, `agent`) are accessed through the
15
+ // `Record<string, unknown>`-typed helpers below rather than the envelope.
16
+ type In = WorkerInputs["pr.persist-round"];
27
17
 
28
18
  // The harness records the agent's full (byte-capped) stdout on the result envelope; keep it
29
19
  // for audit so a human can see what the agent did this round.
@@ -13,11 +13,12 @@
13
13
  // already-recorded edge is a no-op.
14
14
  import type { AppJobHandler } from "@nanobpm/urban";
15
15
  import { ensurePr, parsePr } from "../../app/service.ts";
16
+ import type { WorkerInputs } from "../../nano-generated/worker-io.d.ts";
16
17
 
17
- interface In extends Record<string, unknown> {
18
- prKey: string;
19
- dependsOn?: unknown;
20
- }
18
+ // Input typed off the model data envelope (`RecordDepIn` in merge-loop.bpmn) — ADR 0040. The
19
+ // scalar `dependsOn` carries the agent's `owner/repo#N` refs as one string; `parseDependsOn` below
20
+ // still tolerates the legacy array shape defensively.
21
+ type In = WorkerInputs["pr.record-dependency"];
21
22
 
22
23
  interface DependencyRow {
23
24
  pr_key: string;
@@ -14,13 +14,10 @@
14
14
  import type { AppJobHandler } from "@nanobpm/urban";
15
15
  import { type FeatureRunStatus, featureRuns } from "../../app/feature.ts";
16
16
  import { parsePr } from "../../app/service.ts";
17
+ import type { WorkerInputs } from "../../nano-generated/worker-io.d.ts";
17
18
 
18
- interface In extends Record<string, unknown> {
19
- featureKey: string;
20
- status?: unknown;
21
- pr?: unknown;
22
- summary?: unknown;
23
- }
19
+ // Input typed off the model data envelope (`RecordFeatureIn` in feature.bpmn) — ADR 0040.
20
+ type In = WorkerInputs["pr.record-feature"];
24
21
  interface Out extends Record<string, unknown> {
25
22
  featureStatus: FeatureRunStatus;
26
23
  prKey: string | null;
@@ -25,6 +25,9 @@ interface RawTask {
25
25
  prompt?: unknown;
26
26
  dependsOn?: unknown;
27
27
  }
28
+ // NOTE (issue #211): this worker reads an ARRAY input (`tasks: RawTask[]`), which the scalar-only
29
+ // `nano:dataEnvelope` cannot express, so it keeps a hand-written `interface In` pending a decision
30
+ // on extending the envelope schema to arrays. See the PR body's "array inputs" escalation.
28
31
  interface In extends Record<string, unknown> {
29
32
  planKey: string;
30
33
  tasks?: RawTask[];
@@ -24,13 +24,10 @@ import {
24
24
  type PlanReview,
25
25
  planReviews,
26
26
  } from "../../app/plan.ts";
27
+ import type { WorkerInputs } from "../../nano-generated/worker-io.d.ts";
27
28
 
28
- interface In extends Record<string, unknown> {
29
- planKey: string;
30
- approved?: unknown;
31
- findings?: unknown;
32
- planReviewEpoch?: unknown;
33
- }
29
+ // Input typed off the model data envelope (`RecordPlanReviewIn` in plan-fanout.bpmn) — ADR 0040.
30
+ type In = WorkerInputs["pr.record-plan-review"];
34
31
  interface Out extends Record<string, unknown> {
35
32
  planApproved: boolean;
36
33
  planEscalated: boolean;
@@ -14,10 +14,10 @@
14
14
  import type { AppJobHandler } from "@nanobpm/urban";
15
15
  import { BpmnError } from "@nanobpm/urban";
16
16
  import { planTasks } from "../../app/plan.ts";
17
+ import type { WorkerInputs } from "../../nano-generated/worker-io.d.ts";
17
18
 
18
- interface In extends Record<string, unknown> {
19
- planKey: string;
20
- }
19
+ // Input typed off the model data envelope (`RecordResultsIn` in plan-fanout.bpmn) — ADR 0040.
20
+ type In = WorkerInputs["pr.record-results"];
21
21
 
22
22
  const handler: AppJobHandler<In> = async (job, app) => {
23
23
  const { planKey } = job.variables;
@@ -9,6 +9,10 @@ import {
9
9
  trialMergeTaskId,
10
10
  } from "../../app/trialMerge.ts";
11
11
 
12
+ // NOTE (issue #211): this worker reads ARRAY inputs (`waveOpenHeads`, `conflicts`, `failing`), which
13
+ // the scalar-only `nano:dataEnvelope` cannot express, so it keeps a hand-written `interface In`
14
+ // pending a decision on extending the envelope schema to arrays. See the PR body's "array inputs"
15
+ // escalation.
12
16
  interface In extends Record<string, unknown> {
13
17
  planKey: string;
14
18
  currentWave?: unknown;
@@ -38,6 +38,9 @@ interface Result {
38
38
  interface WaveTaskIn {
39
39
  id?: unknown;
40
40
  }
41
+ // NOTE (issue #211): this worker reads ARRAY inputs (`waveTasks[]`, `waveResults[]`), which the
42
+ // scalar-only `nano:dataEnvelope` cannot express, so it keeps a hand-written `interface In` pending
43
+ // a decision on extending the envelope schema to arrays. See the PR body's "array inputs" escalation.
41
44
  interface In extends Record<string, unknown> {
42
45
  planKey: string;
43
46
  currentWave: number;
@@ -14,12 +14,10 @@
14
14
  // and logged rather than thrown.
15
15
  import type { AppJobHandler } from "@nanobpm/urban";
16
16
  import { resolveTrialMergeAttention } from "../../app/trialMerge.ts";
17
+ import type { WorkerInputs } from "../../nano-generated/worker-io.d.ts";
17
18
 
18
- interface In extends Record<string, unknown> {
19
- planKey: string;
20
- currentWave?: unknown;
21
- trialMergeWave?: unknown;
22
- }
19
+ // Input typed off the model data envelope (`ResolveTrialAttentionIn` in plan-fanout.bpmn) — ADR 0040.
20
+ type In = WorkerInputs["pr.resolve-trial-attention"];
23
21
 
24
22
  const waveNo = (v: unknown): number => {
25
23
  const n = Math.trunc(Number(v));
@@ -5,10 +5,10 @@
5
5
  // agent reflects on real material rather than re-deriving it.
6
6
  import type { AppJobHandler } from "@nanobpm/urban";
7
7
  import { gatherRetro, renderRetroBrief } from "../../app/retro.ts";
8
+ import type { WorkerInputs } from "../../nano-generated/worker-io.d.ts";
8
9
 
9
- interface In extends Record<string, unknown> {
10
- planKey: string;
11
- }
10
+ // Input typed off the model data envelope (`RetroGatherIn` in retro.bpmn) — ADR 0040.
11
+ type In = WorkerInputs["pr.retro-gather"];
12
12
 
13
13
  interface Out extends Record<string, unknown> {
14
14
  retroDigest: string;
@@ -8,16 +8,12 @@
8
8
  // exposes the raw transcript under the `io.nanobpm.agentResult` envelope's `.output`.
9
9
  import type { AppJobHandler } from "@nanobpm/urban";
10
10
  import { recordRetro } from "../../app/retro.ts";
11
+ import type { WorkerInputs } from "../../nano-generated/worker-io.d.ts";
11
12
 
12
13
  const AGENT_RESULT_KEY = "io.nanobpm.agentResult";
13
14
 
14
- interface In extends Record<string, unknown> {
15
- planKey: string;
16
- retroLearnings?: number;
17
- status?: unknown; // filed | skipped | blocked
18
- pr?: unknown; // "<owner>/<repo>#<n>" of the promotion PR, when filed
19
- summary?: unknown;
20
- }
15
+ // Input typed off the model data envelope (`RetroRecordIn` in retro.bpmn) — ADR 0040.
16
+ type In = WorkerInputs["pr.retro-record"];
21
17
 
22
18
  function asStr(v: unknown): string | null {
23
19
  return typeof v === "string" && v.trim() !== "" ? v.trim() : null;
@@ -42,7 +38,7 @@ const handler: AppJobHandler<In> = async (job, app) => {
42
38
  const summary = asStr(job.variables.summary);
43
39
 
44
40
  // biome-ignore lint/plugin: runtime/framework contract boundary for external data shape
45
- const env = job.variables[AGENT_RESULT_KEY] as { output?: unknown } | undefined;
41
+ const env = (job.variables as Record<string, unknown>)[AGENT_RESULT_KEY] as { output?: unknown } | undefined;
46
42
  const report = typeof env?.output === "string" ? env.output : null;
47
43
 
48
44
  await recordRetro(app.data, planKey, {
@@ -16,11 +16,10 @@
16
16
  // immediately (the same 0-task path the flat fan-out already relied on).
17
17
  import type { AppJobHandler } from "@nanobpm/urban";
18
18
  import { plans, planTaskDeps, planTasks } from "../../app/plan.ts";
19
+ import type { WorkerInputs } from "../../nano-generated/worker-io.d.ts";
19
20
 
20
- interface In extends Record<string, unknown> {
21
- planKey: string;
22
- currentWave: number;
23
- }
21
+ // Input typed off the model data envelope (`SelectWaveIn` in plan-fanout.bpmn) — ADR 0040.
22
+ type In = WorkerInputs["pr.select-wave"];
24
23
  interface WaveTaskOut {
25
24
  id: string;
26
25
  title: string;