@nanobpm/nano-workforce 0.107.2 → 0.108.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.108.0](https://github.com/nanobpm/nano-workforce/compare/v0.107.2...v0.108.0) (2026-08-20)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **derivation-parity:** un-park retro to full whole-model parity ([#372](https://github.com/nanobpm/nano-workforce/issues/372)) ([1320838](https://github.com/nanobpm/nano-workforce/commit/1320838251646258ec2151a547ed2b5573ce1168)), closes [nano-ide#405](https://github.com/nano-ide/issues/405) [355/#356](https://github.com/nanobpm/nano-workforce/issues/356) [#371](https://github.com/nanobpm/nano-workforce/issues/371)
|
|
7
|
+
|
|
1
8
|
## [0.107.2](https://github.com/nanobpm/nano-workforce/compare/v0.107.1...v0.107.2) (2026-08-20)
|
|
2
9
|
|
|
3
10
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanobpm/nano-workforce",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.108.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",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@biomejs/biome": "^2.4.11",
|
|
66
66
|
"@nanobpm/urban-testkit": "^0.5.0",
|
|
67
|
-
"@nanobpm/workflow": "^0.
|
|
67
|
+
"@nanobpm/workflow": "^0.13.0",
|
|
68
68
|
"@semantic-release/changelog": "^6.0.3",
|
|
69
69
|
"@semantic-release/git": "^10.0.1",
|
|
70
70
|
"@semantic-release/npm": "^13.1.5",
|
|
@@ -15,19 +15,20 @@
|
|
|
15
15
|
//
|
|
16
16
|
// Any structural drift (b) OR deploy rejection (c) fails the build.
|
|
17
17
|
//
|
|
18
|
-
// PARKED MODELS ARE ACCOUNTED FOR, NOT IGNORED.
|
|
19
|
-
// upstream `@nanobpm/workflow` constructs (see
|
|
20
|
-
// classes). Each parked model must carry a
|
|
21
|
-
// ported nor documented fails this gate, so the
|
|
22
|
-
// parked model flips to a real `flow` upstream, it
|
|
23
|
-
// derive → diff → deploy loop here with NO change to this
|
|
18
|
+
// PARKED MODELS ARE ACCOUNTED FOR, NOT IGNORED. `retro` is a green whole-model parity port; the
|
|
19
|
+
// remaining corpus is parked behind upstream `@nanobpm/workflow` constructs (see
|
|
20
|
+
// test/derivation-parity/flows.ts for the two blocker classes). Each parked model must carry a
|
|
21
|
+
// documented `blockedReason`; a model that is neither ported nor documented fails this gate, so the
|
|
22
|
+
// corpus can never silently lose coverage. As each parked model flips to a real `flow` upstream, it
|
|
23
|
+
// is automatically pulled into the full derive → diff → deploy loop here with NO change to this
|
|
24
|
+
// script.
|
|
24
25
|
//
|
|
25
|
-
// SELF-PROVING CANARY.
|
|
26
|
-
//
|
|
27
|
-
// run a CANARY that proves the oracle's RED path genuinely fires: a faithful
|
|
28
|
-
// green and diffs green, a drifted derivation is CAUGHT by the diff, and a
|
|
29
|
-
// REJECTED by the engine. If any red path fails to fire (the diff misses drift,
|
|
30
|
-
// accepts garbage), the gate fails — the oracle must be able to say no.
|
|
26
|
+
// SELF-PROVING CANARY. To keep the gate honest even while most of the corpus is parked, a gate that
|
|
27
|
+
// merely iterated it could be a near-vacuous green — it could rot without anyone noticing. So before
|
|
28
|
+
// touching the corpus we run a CANARY that proves the oracle's RED path genuinely fires: a faithful
|
|
29
|
+
// derived flow deploys green and diffs green, a drifted derivation is CAUGHT by the diff, and a
|
|
30
|
+
// corrupted model is REJECTED by the engine. If any red path fails to fire (the diff misses drift,
|
|
31
|
+
// or the engine accepts garbage), the gate fails — the oracle must be able to say no.
|
|
31
32
|
|
|
32
33
|
import { mkdtempSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
33
34
|
import { tmpdir } from "node:os";
|
|
@@ -26,7 +26,7 @@ behind an upstream construct, and do **not** relax to node-surface parity_:
|
|
|
26
26
|
|
|
27
27
|
| Model | Top-level start/end | Status |
|
|
28
28
|
| ------------------ | ------------------- | ------ |
|
|
29
|
-
| `retro` | 1 / 1 |
|
|
29
|
+
| `retro` | 1 / 1 | ✅ green — whole-model parity |
|
|
30
30
|
| `convergence-loop` | 1 / 1 | ⛔ parked — class 2 (arbitrary graph) |
|
|
31
31
|
| `spine-demo` | 1 / 2 | ⛔ parked — class 1 (multi start/end) |
|
|
32
32
|
| `readiness-gate` | 1 / 5 | ⛔ parked — class 1 (multi start/end) |
|
|
@@ -34,13 +34,17 @@ behind an upstream construct, and do **not** relax to node-surface parity_:
|
|
|
34
34
|
| `merge-loop` | 1 / 2 | ⛔ parked — class 1 (multi start/end) |
|
|
35
35
|
| `plan-fanout` | 3 / 3 | ⛔ parked — class 1 (multi start/end) |
|
|
36
36
|
|
|
37
|
-
`retro`
|
|
38
|
-
pipeline
|
|
39
|
-
subgraph
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
`retro` is a green whole-model parity port — a single-start/single-end agent
|
|
38
|
+
pipeline (gather → conformance → record-conformance) with a `deviations?`
|
|
39
|
+
conformance-escalation subgraph (#355/#356) and a shared synthesize → record
|
|
40
|
+
tail. Its `record-conformance-ack` service task carries a general
|
|
41
|
+
`<zeebe:ioMapping>` that the stock `task` builder could not emit until
|
|
42
|
+
`@nanobpm/workflow@0.13.0` landed the general service-task `io` construct
|
|
43
|
+
(nano-ide#405); every other element was always expressible (`w.task`+prompt,
|
|
44
|
+
`w.branch`, `w.human`, envelopes). The remaining six goldens are parked across
|
|
45
|
+
**two** distinct blocker classes, each awaiting an upstream `@nanobpm/workflow`
|
|
46
|
+
(nano-ide) construct + re-release (never a golden edit, never relaxed
|
|
47
|
+
acceptance).
|
|
44
48
|
|
|
45
49
|
## The blockers
|
|
46
50
|
|
|
@@ -83,24 +87,6 @@ pinned by a diagnostic in `derivation-parity.test.ts`:
|
|
|
83
87
|
The fix is an **arbitrary-graph / explicit-join (named-target)** builder upstream
|
|
84
88
|
in `@nanobpm/workflow` — a **superset** of the class-1 gap.
|
|
85
89
|
|
|
86
|
-
### Class 3 — general service-task ioMapping (`retro`)
|
|
87
|
-
|
|
88
|
-
`retro` clears classes 1 and 2 (single start/end, structured topology), but its
|
|
89
|
-
golden's `record-conformance-ack` service task carries a **general**
|
|
90
|
-
`<zeebe:ioMapping>` — inputs `=planKey`→`planKey` and
|
|
91
|
-
`=if (is defined(note)) then note else null`→`note`. `@nanobpm/workflow@0.12.0`'s
|
|
92
|
-
`task` builder only emits an ioMapping as a side effect of a `prompt.append` (a
|
|
93
|
-
single `appendPrompt` input); there is no way to declare arbitrary input/output
|
|
94
|
-
mappings on a service task. Every other element of the golden IS expressible
|
|
95
|
-
(`w.task`+prompt, `w.branch` for the `deviations?` gateway, `w.human` for the
|
|
96
|
-
`conformance-escalation` userTask, envelopes) — this one service-task ioMapping
|
|
97
|
-
is the sole gap. The `retro golden needs a general service-task ioMapping the
|
|
98
|
-
stock builder cannot emit` diagnostic pins both halves (the golden needs it; the
|
|
99
|
-
stock builder cannot produce it).
|
|
100
|
-
|
|
101
|
-
The fix is a general `io: { input?, output? }` on the external `task`/`run`
|
|
102
|
-
builder upstream in `@nanobpm/workflow` (**nano-ide#405**).
|
|
103
|
-
|
|
104
90
|
## Resuming this slice
|
|
105
91
|
|
|
106
92
|
The follow-up upstream slices (opened in **nanobpm/nano-ide** per decision path
|
|
@@ -108,8 +94,11 @@ The follow-up upstream slices (opened in **nanobpm/nano-ide** per decision path
|
|
|
108
94
|
|
|
109
95
|
- a terminal / explicit-end (+ multi-start) construct (unblocks class 1), **and**
|
|
110
96
|
- an arbitrary-graph / explicit-join (named-target) builder (unblocks class 2 —
|
|
111
|
-
`convergence-loop`)
|
|
112
|
-
|
|
97
|
+
`convergence-loop`).
|
|
98
|
+
|
|
99
|
+
(The class-3 general service-task `io` mapping has already landed —
|
|
100
|
+
`@nanobpm/workflow@0.13.0`, nano-ide#405 — unparking `retro` to whole-model
|
|
101
|
+
parity.)
|
|
113
102
|
|
|
114
103
|
Then, on a resumed run here:
|
|
115
104
|
|
|
@@ -140,7 +129,7 @@ documented `blockedReason`, so the corpus can never silently lose coverage; a
|
|
|
140
129
|
parked model flips into the full derive → diff → deploy loop automatically the
|
|
141
130
|
moment its `flow` lands — no change to the gate.
|
|
142
131
|
|
|
143
|
-
Because the corpus is (currently)
|
|
132
|
+
Because most of the corpus is (currently) parked, the gate runs a **self-proving
|
|
144
133
|
canary** first: it proves a faithful derivation deploys and diffs green, a
|
|
145
134
|
drifted derivation is caught by the diff, and a corrupted model is rejected by
|
|
146
135
|
the engine — so the oracle's red path can never rot into a vacuous green. The
|
|
@@ -85,8 +85,8 @@ test("class-1 blocked goldens genuinely have multiple top-level start/end events
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
// The two single-start/single-end goldens (retro, convergence-loop) clear
|
|
88
|
-
// class 1; retro is
|
|
89
|
-
// convergence-loop is class-2 blocked
|
|
88
|
+
// class 1; retro is a GREEN whole-model parity port (it runs through
|
|
89
|
+
// `assertDerivationParity` above), convergence-loop is class-2 blocked (below).
|
|
90
90
|
for (const model of ["retro", "convergence-loop"]) {
|
|
91
91
|
const xml = readFileSync(goldenPath(model), "utf8");
|
|
92
92
|
assertEquals(countTag(xml, "startEvent"), 1, `${model} should have one start event`);
|
|
@@ -94,37 +94,6 @@ test("class-1 blocked goldens genuinely have multiple top-level start/end events
|
|
|
94
94
|
}
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
-
// CLASS 3 — retro clears classes 1 & 2 (single start/end, structured topology)
|
|
98
|
-
// but its golden carries a service task with a GENERAL <zeebe:ioMapping> — inputs
|
|
99
|
-
// whose target is NOT `appendPrompt` — which @nanobpm/workflow@0.12.0's `task`
|
|
100
|
-
// builder cannot emit (it only produces an ioMapping via a `prompt.append`, i.e.
|
|
101
|
-
// a lone `appendPrompt` input). Prove both halves: the golden needs it, and the
|
|
102
|
-
// stock builder cannot produce it (awaits nano-ide#405).
|
|
103
|
-
test("retro golden needs a general service-task ioMapping the stock builder cannot emit", () => {
|
|
104
|
-
const xml = readFileSync(goldenPath("retro"), "utf8");
|
|
105
|
-
// (a) The golden has a service task carrying an ioMapping input to a non-prompt
|
|
106
|
-
// target (`record-conformance-ack`: =planKey→planKey, note→note).
|
|
107
|
-
assert(
|
|
108
|
-
/target="planKey"/.test(xml) && /target="note"/.test(xml),
|
|
109
|
-
"retro golden should carry general ioMapping inputs (planKey, note) on record-conformance-ack",
|
|
110
|
-
);
|
|
111
|
-
// (b) The stock `task` builder only ever emits `appendPrompt` as an ioMapping
|
|
112
|
-
// target — never a general input like `note` — so the golden is not
|
|
113
|
-
// derivable until the upstream `io` construct lands.
|
|
114
|
-
const probe = defineFlow("io-probe", (w) => {
|
|
115
|
-
w.task("agent", {
|
|
116
|
-
jobType: "senior:retro",
|
|
117
|
-
prompt: { resourceId: "retro.md", bindingType: "latest", append: "=retroDigest" },
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
const derived = declarativeToBpmn(probe);
|
|
121
|
-
assert(/target="appendPrompt"/.test(derived), "prompt.append should emit an appendPrompt ioMapping input");
|
|
122
|
-
assert(
|
|
123
|
-
!/target="note"/.test(derived) && !/target="planKey"/.test(derived),
|
|
124
|
-
"the stock task builder cannot emit a general (non-appendPrompt) ioMapping input",
|
|
125
|
-
);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
97
|
// CLASS 2 — convergence-loop has a single start/end (clears class 1) but an
|
|
129
98
|
// ARBITRARY control-flow graph the structured-only builder cannot emit. Prove
|
|
130
99
|
// the three specific features against the golden itself.
|
|
@@ -9,12 +9,13 @@
|
|
|
9
9
|
// the structurally-derivable goldens at full whole-model parity, park the rest
|
|
10
10
|
// pending an upstream construct, and do NOT relax to node-surface parity):
|
|
11
11
|
//
|
|
12
|
-
// •
|
|
13
|
-
//
|
|
14
|
-
// + re-release — never a
|
|
12
|
+
// • `retro` is a GREEN whole-model parity port (see below). The remaining six
|
|
13
|
+
// goldens are `blockedReason`-parked, in TWO distinct classes, each awaiting
|
|
14
|
+
// an upstream `@nanobpm/workflow` (nano-ide) construct + re-release — never a
|
|
15
|
+
// golden edit and never relaxed acceptance:
|
|
15
16
|
//
|
|
16
17
|
// (1) MULTI top-level start/end (spine-demo, readiness-gate, feature,
|
|
17
|
-
// merge-loop, plan-fanout). `@nanobpm/workflow
|
|
18
|
+
// merge-loop, plan-fanout). `@nanobpm/workflow` derives EXACTLY
|
|
18
19
|
// ONE `<bpmn:startEvent id="Start">` + ONE `<bpmn:endEvent id="End">`,
|
|
19
20
|
// converging every dangler into that single end (see `Compiler.compile`
|
|
20
21
|
// in the package's `declarative.ts`). Needs a terminal/explicit-end
|
|
@@ -34,23 +35,10 @@
|
|
|
34
35
|
// arbitrary-graph / explicit-join (named-target) builder — a SUPERSET of
|
|
35
36
|
// the class-(1) gap.
|
|
36
37
|
//
|
|
37
|
-
// (3) GENERAL service-task ioMapping (retro). retro WAS a green full-parity
|
|
38
|
-
// port (a linear gather → synthesize → record agent pipeline) until the
|
|
39
|
-
// conformance work (nano-workforce #355/#356) added a conformance-
|
|
40
|
-
// escalation subgraph to its golden. Every new element ports with the
|
|
41
|
-
// stock builder (`w.branch` for the `deviations?` gateway, `w.human` for
|
|
42
|
-
// the `conformance-escalation` userTask, `w.task`+prompt/envelopes for
|
|
43
|
-
// the service tasks) EXCEPT `record-conformance-ack`: a service task with
|
|
44
|
-
// a general <zeebe:ioMapping> (inputs `=planKey`→planKey and
|
|
45
|
-
// `=if (is defined(note)) then note else null`→note). @nanobpm/workflow@
|
|
46
|
-
// 0.12.0's `task` builder only emits an ioMapping via a `prompt.append`
|
|
47
|
-
// (a single `appendPrompt` input), so this task is not derivable. Needs a
|
|
48
|
-
// general `io` on the task/run builder upstream (nano-ide#405).
|
|
49
|
-
//
|
|
50
38
|
// A resumed run flips any parked model to a real `flow` once the corresponding
|
|
51
|
-
// upstream construct lands and `@nanobpm/workflow` is bumped
|
|
39
|
+
// upstream construct lands and `@nanobpm/workflow` is bumped to carry it.
|
|
52
40
|
|
|
53
|
-
import type
|
|
41
|
+
import { type DeclarativeFlow, defineFlow, envelope } from "@nanobpm/workflow";
|
|
54
42
|
|
|
55
43
|
/** One model's port entry: the golden basename plus EITHER the derived flow
|
|
56
44
|
* (when it can be reproduced) OR the reason it is blocked — never both and never
|
|
@@ -72,38 +60,87 @@ export type PortEntry =
|
|
|
72
60
|
readonly blockedReason: string;
|
|
73
61
|
};
|
|
74
62
|
|
|
75
|
-
// ── retro (
|
|
76
|
-
// retro
|
|
77
|
-
// agent pipeline
|
|
78
|
-
// conformance-escalation subgraph
|
|
79
|
-
//
|
|
80
|
-
// `
|
|
81
|
-
//
|
|
82
|
-
//
|
|
63
|
+
// ── retro (GREEN — whole-model parity) ───────────────────────────────────────
|
|
64
|
+
// retro is a single-start/single-end model: a linear gather → conformance →
|
|
65
|
+
// record-conformance agent pipeline, a `deviations?` exclusive gateway guarding a
|
|
66
|
+
// conformance-escalation subgraph (nano-workforce #355/#356), then a shared
|
|
67
|
+
// synthesize → record tail. Every element ports with the stock builder:
|
|
68
|
+
// `w.task`+envelopes for the data-envelope service tasks, `w.task`+prompt for the
|
|
69
|
+
// two agent tasks (`senior:conformance`, `senior:retro`), `w.branch` for the
|
|
70
|
+
// `deviations?` gateway, `w.human` for the `conformance-escalation` userTask, and
|
|
71
|
+
// — since @nanobpm/workflow@0.13.0 landed the general service-task `io` construct
|
|
72
|
+
// (nano-ide#405) — `w.task`+`io` for `record-conformance-ack`'s general
|
|
83
73
|
// <zeebe:ioMapping> (inputs `=planKey`→planKey and
|
|
84
|
-
// `=if (is defined(note)) then note else null`→note)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
74
|
+
// `=if (is defined(note)) then note else null`→note).
|
|
75
|
+
|
|
76
|
+
/** The typed data envelopes retro's non-agent service tasks lift into the model
|
|
77
|
+
* (`nano:shape` + `io.nanobpm.dataEnvelope.in`), matching the golden's shapes. */
|
|
78
|
+
const RetroGatherIn = envelope("RetroGatherIn", { planKey: "string" });
|
|
79
|
+
const RetroRecordIn = envelope("RetroRecordIn", {
|
|
80
|
+
planKey: "string",
|
|
81
|
+
retroLearnings: { type: "integer", optional: true },
|
|
82
|
+
status: { type: "string", optional: true },
|
|
83
|
+
pr: { type: "string", optional: true },
|
|
84
|
+
summary: { type: "string", optional: true },
|
|
85
|
+
});
|
|
86
|
+
const ConformanceRecordIn = envelope("ConformanceRecordIn", {
|
|
87
|
+
planKey: "string",
|
|
88
|
+
status: { type: "string", optional: true },
|
|
89
|
+
commentUrl: { type: "string", optional: true },
|
|
90
|
+
slicesMet: { type: "integer", optional: true },
|
|
91
|
+
slicesReduced: { type: "integer", optional: true },
|
|
92
|
+
slicesNotVerified: { type: "integer", optional: true },
|
|
93
|
+
deviationsRaised: { type: "integer", optional: true },
|
|
94
|
+
deviationsUnraised: { type: "integer", optional: true },
|
|
95
|
+
hasDeviations: { type: "boolean", optional: true },
|
|
96
|
+
summary: { type: "string", optional: true },
|
|
97
|
+
});
|
|
90
98
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
"
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
/** The code-first port of `resources/processes/retro.bpmn`. */
|
|
100
|
+
const retroFlow: DeclarativeFlow = defineFlow(
|
|
101
|
+
"retro",
|
|
102
|
+
{
|
|
103
|
+
gather: { in: RetroGatherIn },
|
|
104
|
+
"record-conformance": { in: ConformanceRecordIn },
|
|
105
|
+
record: { in: RetroRecordIn },
|
|
106
|
+
},
|
|
107
|
+
(w) => {
|
|
108
|
+
w.task("gather", { jobType: "pr.retro-gather" });
|
|
109
|
+
w.task("conformance", {
|
|
110
|
+
jobType: "senior:conformance",
|
|
111
|
+
prompt: { resourceId: "conformance.md", bindingType: "latest", append: "=conformanceDigest" },
|
|
112
|
+
});
|
|
113
|
+
w.task("record-conformance", { jobType: "pr.conformance-record" });
|
|
114
|
+
w.branch("hasDeviations = true", {
|
|
115
|
+
then: (b) => {
|
|
116
|
+
b.human("conformance-escalation", {
|
|
117
|
+
form: "conformance-escalation",
|
|
118
|
+
candidateGroups: "operators",
|
|
119
|
+
});
|
|
120
|
+
b.task("record-conformance-ack", {
|
|
121
|
+
jobType: "pr.conformance-ack",
|
|
122
|
+
io: {
|
|
123
|
+
input: [
|
|
124
|
+
{ source: "=planKey", target: "planKey" },
|
|
125
|
+
{ source: "=if (is defined(note)) then note else null", target: "note" },
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
w.task("synthesize", {
|
|
132
|
+
jobType: "senior:retro",
|
|
133
|
+
prompt: { resourceId: "retro.md", bindingType: "latest", append: "=retroDigest" },
|
|
134
|
+
});
|
|
135
|
+
w.task("record", { jobType: "pr.retro-record" });
|
|
136
|
+
},
|
|
137
|
+
);
|
|
101
138
|
|
|
102
139
|
/** The single-top-level-end/start compiler limitation, reused as the
|
|
103
140
|
* `blockedReason` for every golden that has more than one top-level start
|
|
104
141
|
* and/or end event. */
|
|
105
142
|
const MULTI_START_END_BLOCK =
|
|
106
|
-
"blocked: @nanobpm/workflow
|
|
143
|
+
"blocked: the published @nanobpm/workflow compiler derives a single top-level start/end and " +
|
|
107
144
|
"converges all danglers into one <endEvent id=\"End\">; this golden has " +
|
|
108
145
|
"multiple top-level start and/or end events, which the published compiler " +
|
|
109
146
|
"cannot reproduce. Awaits an upstream terminal/explicit-end (+ multi-start) " +
|
|
@@ -111,7 +148,7 @@ const MULTI_START_END_BLOCK =
|
|
|
111
148
|
|
|
112
149
|
/** All seven ports, keyed by model, in the epic's stated authoring order. */
|
|
113
150
|
export const PORTS: readonly PortEntry[] = [
|
|
114
|
-
{ model: "retro",
|
|
151
|
+
{ model: "retro", flow: retroFlow },
|
|
115
152
|
{
|
|
116
153
|
model: "spine-demo",
|
|
117
154
|
blockedReason: `${MULTI_START_END_BLOCK} (spine-demo: 1 start, 2 ends)`,
|
|
@@ -128,7 +165,7 @@ export const PORTS: readonly PortEntry[] = [
|
|
|
128
165
|
model: "convergence-loop",
|
|
129
166
|
blockedReason:
|
|
130
167
|
"blocked (arbitrary control-flow graph): single top-level start/end, but " +
|
|
131
|
-
"its topology is not expressible with @nanobpm/workflow
|
|
168
|
+
"its topology is not expressible with the published @nanobpm/workflow's " +
|
|
132
169
|
"structured-only builder (loop/switch/branch). Proven in the test suite: " +
|
|
133
170
|
"the loop head `review-round` is a serviceTask that merges 3 back-edges " +
|
|
134
171
|
"directly (in=3), but loop() always inserts an exclusive-gateway head " +
|