@nanobpm/nano-workforce 0.65.0 → 0.66.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/AGENTS.md +19 -7
- package/CHANGELOG.md +7 -0
- package/SPEC.md +12 -3
- package/package.json +1 -1
- package/resources/processes/plan-fanout.bpmn +58 -0
- package/workers/record-plan/worker.ts +5 -14
- package/workers/record-trial-merge/worker.ts +6 -14
- package/workers/record-wave/worker.ts +6 -19
package/AGENTS.md
CHANGED
|
@@ -142,13 +142,25 @@ instead of rediscovering them:
|
|
|
142
142
|
per-child isolation in the testkit). Only vars read by an in-subprocess gateway
|
|
143
143
|
need this — output-only vars (e.g. `summary`, `pr`) are fine.
|
|
144
144
|
|
|
145
|
-
## Data envelopes are scalar-only
|
|
146
|
-
|
|
147
|
-
`nano:dataEnvelope`
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
145
|
+
## Data envelopes: message payloads are scalar-only; worker I/O shapes support arrays
|
|
146
|
+
|
|
147
|
+
Two different `nano:dataEnvelope` uses have different rules — don't conflate them:
|
|
148
|
+
|
|
149
|
+
- **Message payloads are scalar-only.** A `nano:dataEnvelope` that crosses the
|
|
150
|
+
engine's message correlation supports only **scalar** `nano:extend` types
|
|
151
|
+
(`string`, `integer`, `datetime`, `boolean`) — **not arrays**. When a message
|
|
152
|
+
must carry a list (e.g. failing check names), join it to a scalar (e.g.
|
|
153
|
+
`\n`-separated) in the publisher before it crosses the envelope, and split it on
|
|
154
|
+
the far side if needed.
|
|
155
|
+
- **Worker job-I/O shapes support `list="true"` arrays.** A service task's
|
|
156
|
+
`io.nanobpm.dataEnvelope.in`/`.out` shape is codegen/typing-only (no runtime
|
|
157
|
+
filtering), so `nano:extend … list="true"` (scalar arrays, e.g. `dependsOn`)
|
|
158
|
+
and `nano:reference … list="true"` (object arrays) are supported end-to-end and
|
|
159
|
+
derive to `T[]` in the generated types (per #211 — e.g.
|
|
160
|
+
`RecordPlanIn.tasks: RecordPlanTask[]`, `RecordWaveIn.waveResults`). Prefer
|
|
161
|
+
deriving a worker's array inputs from the model this way over a hand-typed
|
|
162
|
+
`interface In`; don't "fix" a modelled `list="true"` array back to a joined
|
|
163
|
+
scalar.
|
|
152
164
|
|
|
153
165
|
## Urban page runtime: rendering primitives are not JS-truthy
|
|
154
166
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [0.66.0](https://github.com/nanobpm/nano-workforce/compare/v0.65.0...v0.66.0) (2026-08-14)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* derive record-plan/record-wave/record-trial-merge array inputs from the model ([#212](https://github.com/nanobpm/nano-workforce/issues/212)) ([95f1694](https://github.com/nanobpm/nano-workforce/commit/95f16944918b99107e784eed5ab69a5962365ec4)), closes [#211](https://github.com/nanobpm/nano-workforce/issues/211) [#211](https://github.com/nanobpm/nano-workforce/issues/211) [#211](https://github.com/nanobpm/nano-workforce/issues/211) [#211](https://github.com/nanobpm/nano-workforce/issues/211)
|
|
7
|
+
|
|
1
8
|
# [0.65.0](https://github.com/nanobpm/nano-workforce/compare/v0.64.0...v0.65.0) (2026-08-13)
|
|
2
9
|
|
|
3
10
|
|
package/SPEC.md
CHANGED
|
@@ -488,9 +488,18 @@ Start(issue) → plan → record-plan → implement (parallel MI) → record-res
|
|
|
488
488
|
it raises a non-retryable `NO_WORK_DISPATCHED` incident instead of completing
|
|
489
489
|
green — a no-op run must not masquerade as success (issue #86).
|
|
490
490
|
|
|
491
|
-
**
|
|
492
|
-
|
|
493
|
-
`
|
|
491
|
+
**App-worker payloads are typed from the model.** Each `pr.*` service task
|
|
492
|
+
(`record-plan`, `record-wave`, `record-trial-merge`, `select-wave`,
|
|
493
|
+
`record-plan-review`, `resolve-trial-attention`, `record-results`, …) carries an
|
|
494
|
+
`io.nanobpm.dataEnvelope.in` shape whose `nano:shapes` express the `tasks`/`results`
|
|
495
|
+
lists directly: worker job-I/O envelopes support `list="true"` arrays — both
|
|
496
|
+
`nano:extend … list="true"` scalar arrays (e.g. `dependsOn`, `conflicts`) and
|
|
497
|
+
`nano:reference … list="true"` object arrays (e.g. `RecordPlanIn.tasks`,
|
|
498
|
+
`RecordWaveIn.waveResults`) — which derive to `T[]` in the generated task types
|
|
499
|
+
(per #211). This job-I/O shape is codegen/typing-only (no runtime filtering); the
|
|
500
|
+
`scalar-only` constraint applies only to **message payload** envelopes that cross
|
|
501
|
+
correlation at runtime, not to these worker in/out shapes. The **agent** tasks
|
|
502
|
+
(`senior:*`) still self-type `job.variables` inline (like `finalize`).
|
|
494
503
|
|
|
495
504
|
**Domain model** (`db/migrations/004_planning.sql`): `plans` (one row per issue) +
|
|
496
505
|
`plan_tasks` (one row per slice, tracking its `status`/`pr_key`/`summary`).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanobpm/nano-workforce",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.66.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",
|
|
@@ -12,6 +12,55 @@
|
|
|
12
12
|
<nano:extend name="repo" type="string" />
|
|
13
13
|
<nano:extend name="baseBranch" type="string" />
|
|
14
14
|
</nano:shape>
|
|
15
|
+
<nano:shape id="RecordPlanTask" name="Record plan — raw planner task">
|
|
16
|
+
<nano:extend name="id" type="string" optional="true" />
|
|
17
|
+
<nano:extend name="title" type="string" optional="true" />
|
|
18
|
+
<nano:extend name="prompt" type="string" optional="true" />
|
|
19
|
+
<nano:extend name="dependsOn" type="string" list="true" optional="true" />
|
|
20
|
+
</nano:shape>
|
|
21
|
+
<nano:shape id="RecordPlanIn" name="Record plan — input">
|
|
22
|
+
<nano:extend name="planKey" type="string" />
|
|
23
|
+
<nano:reference name="tasks" ref="RecordPlanTask" list="true" />
|
|
24
|
+
<nano:extend name="note" type="string" optional="true" />
|
|
25
|
+
</nano:shape>
|
|
26
|
+
<nano:shape id="RecordWaveTaskRef" name="Record wave — task ref">
|
|
27
|
+
<nano:extend name="id" type="string" optional="true" />
|
|
28
|
+
</nano:shape>
|
|
29
|
+
<nano:shape id="RecordWaveDelta" name="Record wave — task delta">
|
|
30
|
+
<nano:extend name="contractChange" type="string" optional="true" />
|
|
31
|
+
<nano:extend name="newlyTouches" type="string" list="true" optional="true" />
|
|
32
|
+
<nano:extend name="affectsTasks" type="string" list="true" optional="true" />
|
|
33
|
+
<nano:extend name="constraint" type="string" optional="true" />
|
|
34
|
+
</nano:shape>
|
|
35
|
+
<nano:shape id="RecordWaveResult" name="Record wave — agent result">
|
|
36
|
+
<nano:extend name="status" type="string" optional="true" />
|
|
37
|
+
<nano:extend name="summary" type="string" optional="true" />
|
|
38
|
+
<nano:extend name="pr" type="string" optional="true" />
|
|
39
|
+
<nano:extend name="delta" type="RecordWaveDelta" optional="true" />
|
|
40
|
+
</nano:shape>
|
|
41
|
+
<nano:shape id="RecordWaveIn" name="Record wave — input">
|
|
42
|
+
<nano:extend name="planKey" type="string" />
|
|
43
|
+
<nano:extend name="currentWave" type="integer" />
|
|
44
|
+
<nano:extend name="waveCount" type="integer" />
|
|
45
|
+
<nano:reference name="waveTasks" ref="RecordWaveTaskRef" list="true" />
|
|
46
|
+
<nano:reference name="waveResults" ref="RecordWaveResult" list="true" />
|
|
47
|
+
</nano:shape>
|
|
48
|
+
<nano:shape id="RecordTrialMergeHead" name="Record trial merge — open head">
|
|
49
|
+
<nano:extend name="repo" type="string" />
|
|
50
|
+
<nano:extend name="prNumber" type="integer" />
|
|
51
|
+
<nano:extend name="headRef" type="string" optional="true" />
|
|
52
|
+
<nano:extend name="headSha" type="string" optional="true" />
|
|
53
|
+
</nano:shape>
|
|
54
|
+
<nano:shape id="RecordTrialMergeIn" name="Record trial merge — input">
|
|
55
|
+
<nano:extend name="planKey" type="string" />
|
|
56
|
+
<nano:extend name="currentWave" type="integer" optional="true" />
|
|
57
|
+
<nano:extend name="trialMergeWave" type="integer" optional="true" />
|
|
58
|
+
<nano:reference name="waveOpenHeads" ref="RecordTrialMergeHead" list="true" />
|
|
59
|
+
<nano:extend name="result" type="string" optional="true" />
|
|
60
|
+
<nano:extend name="conflicts" type="string" list="true" optional="true" />
|
|
61
|
+
<nano:extend name="failing" type="string" list="true" optional="true" />
|
|
62
|
+
<nano:extend name="summary" type="string" optional="true" />
|
|
63
|
+
</nano:shape>
|
|
15
64
|
<nano:shape id="RecordPlanReviewIn" name="Record plan review — input">
|
|
16
65
|
<nano:extend name="planKey" type="string" />
|
|
17
66
|
<nano:extend name="approved" type="boolean" optional="true" />
|
|
@@ -68,6 +117,9 @@
|
|
|
68
117
|
<bpmn:serviceTask id="record-plan" name="Record plan & levelize">
|
|
69
118
|
<bpmn:extensionElements>
|
|
70
119
|
<zeebe:taskDefinition type="pr.record-plan" />
|
|
120
|
+
<zeebe:properties>
|
|
121
|
+
<zeebe:property name="io.nanobpm.dataEnvelope.in" value="RecordPlanIn" />
|
|
122
|
+
</zeebe:properties>
|
|
71
123
|
</bpmn:extensionElements>
|
|
72
124
|
<bpmn:incoming>f_toRecordPlan</bpmn:incoming>
|
|
73
125
|
<bpmn:outgoing>f_toReviewPlan</bpmn:outgoing>
|
|
@@ -215,6 +267,9 @@
|
|
|
215
267
|
<bpmn:serviceTask id="record-wave" name="Record wave & hand off">
|
|
216
268
|
<bpmn:extensionElements>
|
|
217
269
|
<zeebe:taskDefinition type="pr.record-wave" />
|
|
270
|
+
<zeebe:properties>
|
|
271
|
+
<zeebe:property name="io.nanobpm.dataEnvelope.in" value="RecordWaveIn" />
|
|
272
|
+
</zeebe:properties>
|
|
218
273
|
</bpmn:extensionElements>
|
|
219
274
|
<bpmn:incoming>f_toRecordWave</bpmn:incoming>
|
|
220
275
|
<bpmn:outgoing>f_toTrialNeeded</bpmn:outgoing>
|
|
@@ -246,6 +301,9 @@
|
|
|
246
301
|
<bpmn:serviceTask id="record-trial-merge" name="Record trial merge">
|
|
247
302
|
<bpmn:extensionElements>
|
|
248
303
|
<zeebe:taskDefinition type="pr.record-trial-merge" />
|
|
304
|
+
<zeebe:properties>
|
|
305
|
+
<zeebe:property name="io.nanobpm.dataEnvelope.in" value="RecordTrialMergeIn" />
|
|
306
|
+
</zeebe:properties>
|
|
249
307
|
</bpmn:extensionElements>
|
|
250
308
|
<bpmn:incoming>f_toRecordTrialMerge</bpmn:incoming>
|
|
251
309
|
<bpmn:outgoing>f_toGwTrial</bpmn:outgoing>
|
|
@@ -18,21 +18,12 @@
|
|
|
18
18
|
import type { AppJobHandler } from "@nanobpm/urban";
|
|
19
19
|
import { planTaskDeps, planTasks } from "../../app/plan.ts";
|
|
20
20
|
import { computeWaves, WaveError, type WaveTask } from "../../app/waves.ts";
|
|
21
|
+
import type { WorkerInputs } from "../../nano-generated/worker-io.d.ts";
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
dependsOn?: unknown;
|
|
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.
|
|
31
|
-
interface In extends Record<string, unknown> {
|
|
32
|
-
planKey: string;
|
|
33
|
-
tasks?: RawTask[];
|
|
34
|
-
note?: string;
|
|
35
|
-
}
|
|
23
|
+
// Input typed off the model data envelope (`RecordPlanIn` in plan-fanout.bpmn) — ADR 0040. The
|
|
24
|
+
// `tasks[]` array field is a `nano:reference` to the `RecordPlanTask` shape (`list="true"`), so it
|
|
25
|
+
// resolves to `RecordPlanTask[]` with no hand-written interface.
|
|
26
|
+
type In = WorkerInputs["pr.record-plan"];
|
|
36
27
|
interface NormalTask {
|
|
37
28
|
id: string;
|
|
38
29
|
title: string;
|
|
@@ -8,21 +8,13 @@ import {
|
|
|
8
8
|
trialMergeDecision,
|
|
9
9
|
trialMergeTaskId,
|
|
10
10
|
} from "../../app/trialMerge.ts";
|
|
11
|
+
import type { WorkerInputs } from "../../nano-generated/worker-io.d.ts";
|
|
11
12
|
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
planKey: string;
|
|
18
|
-
currentWave?: unknown;
|
|
19
|
-
trialMergeWave?: unknown;
|
|
20
|
-
waveOpenHeads?: unknown;
|
|
21
|
-
result?: unknown;
|
|
22
|
-
conflicts?: unknown;
|
|
23
|
-
failing?: unknown;
|
|
24
|
-
summary?: unknown;
|
|
25
|
-
}
|
|
13
|
+
// Input typed off the model data envelope (`RecordTrialMergeIn` in plan-fanout.bpmn) — ADR 0040.
|
|
14
|
+
// The `waveOpenHeads[]` array field is a `nano:reference` to the `RecordTrialMergeHead` shape, and
|
|
15
|
+
// `conflicts[]` / `failing[]` are scalar `list="true"` extends, so all three resolve from the model
|
|
16
|
+
// with no hand-written interface.
|
|
17
|
+
type In = WorkerInputs["pr.record-trial-merge"];
|
|
26
18
|
interface Out extends Record<string, unknown> {
|
|
27
19
|
trialMergeRed: boolean;
|
|
28
20
|
question?: string;
|
|
@@ -28,26 +28,13 @@ import {
|
|
|
28
28
|
import { parsePr, submitPr } from "../../app/service.ts";
|
|
29
29
|
import { parseTaskDelta, readTaskDeltas, recordTaskDelta } from "../../app/taskDelta.ts";
|
|
30
30
|
import { shouldRunTrialMerge, type TrialMergeHead } from "../../app/trialMerge.ts";
|
|
31
|
+
import type { WorkerInputs } from "../../nano-generated/worker-io.d.ts";
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
interface WaveTaskIn {
|
|
39
|
-
id?: unknown;
|
|
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.
|
|
44
|
-
interface In extends Record<string, unknown> {
|
|
45
|
-
planKey: string;
|
|
46
|
-
currentWave: number;
|
|
47
|
-
waveCount: number;
|
|
48
|
-
waveTasks?: WaveTaskIn[];
|
|
49
|
-
waveResults?: Result[];
|
|
50
|
-
}
|
|
33
|
+
// Input typed off the model data envelope (`RecordWaveIn` in plan-fanout.bpmn) — ADR 0040. The
|
|
34
|
+
// `waveTasks[]` / `waveResults[]` array fields are `nano:reference`s to the `RecordWaveTaskRef` /
|
|
35
|
+
// `RecordWaveResult` shapes (`list="true"`), so they resolve to `<Shape>[]` with no hand-written
|
|
36
|
+
// interface.
|
|
37
|
+
type In = WorkerInputs["pr.record-wave"];
|
|
51
38
|
interface Out extends Record<string, unknown> {
|
|
52
39
|
currentWave: number;
|
|
53
40
|
hasMoreWaves: boolean;
|