@nanobpm/nano-workforce 0.182.3 → 0.183.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,9 @@
1
+ ## [0.183.0](https://github.com/nanobpm/nano-workforce/compare/v0.182.3...v0.183.0) (2026-09-07)
2
+
3
+ ### Features
4
+
5
+ * **agent:** mark senior:* tasks as engine-native external AgentTasks ([#748](https://github.com/nanobpm/nano-workforce/issues/748)) ([806eea5](https://github.com/nanobpm/nano-workforce/commit/806eea522490a999747ad4814fc92847dab3a6e4)), closes [#464](https://github.com/nanobpm/nano-workforce/issues/464) [#745](https://github.com/nanobpm/nano-workforce/issues/745) [#746](https://github.com/nanobpm/nano-workforce/issues/746) [#747](https://github.com/nanobpm/nano-workforce/issues/747) [Magikcraft/nano-bpm#1137](https://github.com/Magikcraft/nano-bpm/issues/1137)
6
+
1
7
  ## [0.182.3](https://github.com/nanobpm/nano-workforce/compare/v0.182.2...v0.182.3) (2026-09-06)
2
8
 
3
9
  ### Bug Fixes
@@ -0,0 +1,74 @@
1
+ // Tests for the engine-native AgentTask marker (issue #745, umbrella #746 — Camunda 8.10 parity),
2
+ // including the defect-class regression guard: every deployed prompt-bearing `senior:*` agent task
3
+ // must carry `<zeebe:agentDefinition agentType="external" />` alongside its `<zeebe:taskDefinition>`,
4
+ // so the worker harness mints an engine-native AgentInstance for it. A newly-added agent task that
5
+ // forgets the marker never persists durable AgentHistory — so it fails CI here instead of silently
6
+ // drifting.
7
+ import { readFileSync, readdirSync } from "node:fs";
8
+ import { test } from "node:test";
9
+ import { dirname, join, relative } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+ import { assert, assertEquals } from "#test-assert";
12
+ import { agentTaskTypesMissingExternalMarker, promptBearingTaskTypes } from "./job-types.ts";
13
+
14
+ const PROCESSES_DIR = join(dirname(fileURLToPath(import.meta.url)), "../../../resources/processes");
15
+
16
+ // urban deploys `resources/` recursively (every file at any depth), so a process
17
+ // model added under a subdirectory would still deploy — walk recursively here too,
18
+ // or the guard would miss it and let an unmarked agent task slip through.
19
+ function bpmnFiles(): string[] {
20
+ const walk = (dir: string): string[] =>
21
+ readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
22
+ const full = join(dir, entry.name);
23
+ if (entry.isDirectory()) return walk(full);
24
+ return entry.name.endsWith(".bpmn") ? [relative(PROCESSES_DIR, full)] : [];
25
+ });
26
+ return walk(PROCESSES_DIR).sort();
27
+ }
28
+
29
+ test("agentTaskTypesMissingExternalMarker flags a prompt-bearing agent task with no external marker", () => {
30
+ const xml = `
31
+ <bpmn:serviceTask id="agent">
32
+ <bpmn:extensionElements>
33
+ <zeebe:taskDefinition type="senior:feature" />
34
+ <zeebe:linkedResources>
35
+ <zeebe:linkedResource resourceId="feature.md" resourceType="GenericScript" linkName="prompt" />
36
+ </zeebe:linkedResources>
37
+ </bpmn:extensionElements>
38
+ </bpmn:serviceTask>`;
39
+ assertEquals(agentTaskTypesMissingExternalMarker(xml), ["senior:feature"]);
40
+ });
41
+
42
+ test("agentTaskTypesMissingExternalMarker passes a marked agent task and ignores a plain host task", () => {
43
+ const xml = `
44
+ <bpmn:serviceTask id="host">
45
+ <bpmn:extensionElements>
46
+ <zeebe:taskDefinition type="pr.finalize" />
47
+ </bpmn:extensionElements>
48
+ </bpmn:serviceTask>
49
+ <bpmn:serviceTask id="agent">
50
+ <bpmn:extensionElements>
51
+ <zeebe:taskDefinition type="senior:feature" />
52
+ <zeebe:agentDefinition agentType="external" />
53
+ <zeebe:linkedResources>
54
+ <zeebe:linkedResource resourceId="feature.md" resourceType="GenericScript" linkName="prompt" />
55
+ </zeebe:linkedResources>
56
+ </bpmn:extensionElements>
57
+ </bpmn:serviceTask>`;
58
+ assertEquals(agentTaskTypesMissingExternalMarker(xml), []);
59
+ });
60
+
61
+ test("DEFECT-CLASS GUARD: every deployed prompt-bearing agent task carries the external AgentTask marker", () => {
62
+ let anyAgentTasks = false;
63
+ for (const file of bpmnFiles()) {
64
+ const xml = readFileSync(join(PROCESSES_DIR, file), "utf8");
65
+ if (promptBearingTaskTypes(xml).length > 0) anyAgentTasks = true;
66
+ const missing = agentTaskTypesMissingExternalMarker(xml);
67
+ assert(
68
+ missing.length === 0,
69
+ `${file}: agent task(s) missing <zeebe:agentDefinition agentType="external" /> — ${missing.join(", ")}`,
70
+ );
71
+ }
72
+ // Sanity: the models really do declare agent tasks (guard is not vacuously green).
73
+ assert(anyAgentTasks, "the deployed models declare prompt-bearing agent tasks");
74
+ });
@@ -49,6 +49,10 @@ export function jobTypeToRoutingToken(jobType: string): string | undefined {
49
49
  const SERVICE_TASK = /<(?:\w+:)?serviceTask\b[\s\S]*?<\/(?:\w+:)?serviceTask>/g;
50
50
  const TASK_DEFINITION_TYPE = /<(?:\w+:)?taskDefinition\b[^>]*\btype="([^"]*)"/;
51
51
  const PROMPT_LINK = /<(?:\w+:)?linkedResource\b[^>]*\blinkName="prompt"/;
52
+ // The engine-native AgentTask marker (issue #745): a `<zeebe:agentDefinition agentType="external" />`
53
+ // sibling of the `<zeebe:taskDefinition>` inside a `senior:*` agent task's extensionElements. It is
54
+ // what makes the element eligible for engine-native AgentInstance minting by the worker harness.
55
+ const EXTERNAL_AGENT_MARKER = /<(?:\w+:)?agentDefinition\b[^>]*\bagentType="external"/;
52
56
 
53
57
  /**
54
58
  * Scan one BPMN document for the job types of its PROMPT-BEARING service tasks — the deployed fleet
@@ -68,3 +72,25 @@ export function promptBearingTaskTypes(xml: string): string[] {
68
72
  }
69
73
  return types;
70
74
  }
75
+
76
+ /**
77
+ * Scan one BPMN document for the job types of PROMPT-BEARING agent service tasks that are MISSING the
78
+ * engine-native AgentTask marker `<zeebe:agentDefinition agentType="external" />` (issue #745). Every
79
+ * deployed `senior:*` agent task must carry the marker so the worker harness mints an AgentInstance
80
+ * for it; a newly-added agent task that forgets it is a silent drift surface (its run never persists
81
+ * durable AgentHistory), so the regression guard fails CI. Returns the offending task types in
82
+ * first-occurrence order (empty when every agent task is marked).
83
+ */
84
+ export function agentTaskTypesMissingExternalMarker(xml: string): string[] {
85
+ const seen = new Set<string>();
86
+ const missing: string[] = [];
87
+ for (const [block] of xml.matchAll(SERVICE_TASK)) {
88
+ if (!PROMPT_LINK.test(block)) continue;
89
+ if (EXTERNAL_AGENT_MARKER.test(block)) continue;
90
+ const type = block.match(TASK_DEFINITION_TYPE)?.[1];
91
+ if (type === undefined || type.length === 0 || seen.has(type)) continue;
92
+ seen.add(type);
93
+ missing.push(type);
94
+ }
95
+ return missing;
96
+ }
package/app/contracts.ts CHANGED
@@ -479,6 +479,14 @@ export const WIRE_CONTRACTS = {
479
479
  "The proxy-safe single-stream transcript READ URL (issue #744): `GET <base>/app/api/agentic/transcripts?stream=<id>&from=<n>` — the stream id rides a QUERY value, NEVER a path segment, because the Nano Console gateway proxy peels one percent-encoding layer before the app routes: an encoded slash (%2F) in a PATH segment arrives as a real / and splits a slash-bearing worker-instance id (`34:<instance>/<jobKey>`) into an extra segment, so the legacy `GET /app/api/agentic/transcripts/{stream}` route 404s behind the proxy (the cockpit past-session replay rendered empty). The path form stays served for back-compat and is proxy-safe ONLY for the slash-free `job:<jobKey>` ids it is seeded with (the worker-emitted `transcriptUrl` = `transcriptUrlBaseFor()` + `jobStream()`, a bare concatenation, must remain resolvable both directly and behind the proxy). ONE builder: `transcriptReadUrlFor()` in app/agentic/transcript-url.ts, served by ONE canonical read (`readSingleTranscript` in app/agentic/transcript-read.ts) shared by both routes; the browser adapter pages/cockpit/mount.js carries a hand-maintained twin (it cannot import server modules). Never put a stream id in a path segment again — do not re-declare a synonym scheme.",
480
480
  shape: "GET <transcriptsEndpoint>?stream=<percent-encoded stream id>[&from=<non-negative integer offset>] → AgenticTranscriptData | ErrorBody",
481
481
  },
482
+ "agentTask.agentDefinition": {
483
+ category: "wire",
484
+ name: "agentTask.agentDefinition",
485
+ owner: "resources/processes/*.bpmn",
486
+ semantics:
487
+ "The engine-native AgentTask marker (issue #745, umbrella #746 — Camunda 8.10 parity). Every `senior:*` agent service task carries `<zeebe:agentDefinition agentType=\"external\" />` INSIDE its `<bpmn:extensionElements>`, COEXISTING with the existing `<zeebe:taskDefinition type=\"senior:*\"/>` dispatch verb (the verb stays, per #464). The marker makes the element eligible for engine-native AgentInstance minting by the worker harness (jwulf/c8ctl-plugin-nano#194): the harness mints Create/Update/Complete AgentInstance/AgentHistory records against the pinned engine (`@nanobpm/engine-wasm` 0.8.6, broker REST, SDK) while the element still emits its NORMAL `senior:*` job. `agentType=\"external\"` means the agent runs OUTSIDE the engine (a remote fleet worker), not an engine-embedded model call. This is the PRODUCER half; the durable AgentInstance/AgentHistory it mints is read back by the Cockpit historical view via `searchAgentInstanceHistory` (the CONSUMER half, deferred until the broker read API is reachable from the app's EngineClient). It is authored in the hand-written BPMN semantic model, NOT the generated `<bpmndi:…>` DI, and survives `npm run layout` untouched. Add the marker to a NEW `senior:*` agent task — never a second/synonym marker element.",
488
+ shape: '<zeebe:agentDefinition agentType="external" /> (sibling of <zeebe:taskDefinition> in a senior:* service task\'s extensionElements)',
489
+ },
482
490
  } as const satisfies Record<string, WireContract>;
483
491
 
484
492
  export const TYPE_CONTRACTS = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanobpm/nano-workforce",
3
- "version": "0.182.3",
3
+ "version": "0.183.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",
@@ -70,7 +70,7 @@
70
70
  },
71
71
  "devDependencies": {
72
72
  "@biomejs/biome": "^2.4.11",
73
- "@nanobpm/urban-testkit": "^1.2.0",
73
+ "@nanobpm/urban-testkit": "^1.3.0",
74
74
  "@nanobpm/workflow": "^0.14.0",
75
75
  "@semantic-release/changelog": "^7.0.0",
76
76
  "@semantic-release/git": "^11.0.0",
@@ -83,6 +83,7 @@
83
83
  "yaml": "^2.9.0"
84
84
  },
85
85
  "overrides": {
86
+ "@nanobpm/engine-wasm": "^0.9.0",
86
87
  "@semantic-release/npm": "^13.1.5"
87
88
  }
88
89
  }
@@ -100,6 +100,7 @@
100
100
  <bpmn:serviceTask id="review-round" name="Review round (agent)">
101
101
  <bpmn:extensionElements>
102
102
  <zeebe:taskDefinition type="senior:pr-review" />
103
+ <zeebe:agentDefinition agentType="external" />
103
104
  <zeebe:linkedResources>
104
105
  <zeebe:linkedResource resourceId="prompts/review-round.md" bindingType="latest" resourceType="GenericScript" linkName="prompt" />
105
106
  </zeebe:linkedResources>
@@ -320,6 +321,7 @@
320
321
  <bpmn:serviceTask id="classify-scope" name="Scope classifier (agent)">
321
322
  <bpmn:extensionElements>
322
323
  <zeebe:taskDefinition type="senior:scope-classify" />
324
+ <zeebe:agentDefinition agentType="external" />
323
325
  <zeebe:linkedResources>
324
326
  <zeebe:linkedResource resourceId="prompts/scope-classify.md" bindingType="latest" resourceType="GenericScript" linkName="prompt" />
325
327
  </zeebe:linkedResources>
@@ -16,6 +16,7 @@
16
16
  <bpmn:serviceTask id="implement-task" name="Implement (agent)">
17
17
  <bpmn:extensionElements>
18
18
  <zeebe:taskDefinition type="senior:feature" />
19
+ <zeebe:agentDefinition agentType="external" />
19
20
  <zeebe:linkedResources>
20
21
  <zeebe:linkedResource resourceId="prompts/feature.md" bindingType="latest" resourceType="GenericScript" linkName="prompt" />
21
22
  </zeebe:linkedResources>
@@ -7,6 +7,7 @@
7
7
  <bpmn:serviceTask id="trial-merge" name="Trial merge (agent)">
8
8
  <bpmn:extensionElements>
9
9
  <zeebe:taskDefinition type="senior:trial-merge" />
10
+ <zeebe:agentDefinition agentType="external" />
10
11
  <zeebe:linkedResources>
11
12
  <zeebe:linkedResource resourceId="prompts/trial-merge.md" bindingType="latest" resourceType="GenericScript" linkName="prompt" />
12
13
  </zeebe:linkedResources>
@@ -339,6 +339,7 @@
339
339
  <bpmn:serviceTask id="fix-ci" name="Fix CI (agent)">
340
340
  <bpmn:extensionElements>
341
341
  <zeebe:taskDefinition type="senior:fix-ci" />
342
+ <zeebe:agentDefinition agentType="external" />
342
343
  <zeebe:linkedResources>
343
344
  <zeebe:linkedResource resourceId="prompts/fix-ci.md" bindingType="latest" resourceType="GenericScript" linkName="prompt" />
344
345
  </zeebe:linkedResources>
@@ -496,6 +497,7 @@
496
497
  <bpmn:serviceTask id="rebase" name="Rebase (agent)">
497
498
  <bpmn:extensionElements>
498
499
  <zeebe:taskDefinition type="senior:rebase" />
500
+ <zeebe:agentDefinition agentType="external" />
499
501
  <zeebe:linkedResources>
500
502
  <zeebe:linkedResource resourceId="prompts/rebase.md" bindingType="latest" resourceType="GenericScript" linkName="prompt" />
501
503
  </zeebe:linkedResources>
@@ -260,6 +260,7 @@
260
260
  <bpmn:serviceTask id="plan" name="Plan (agent)">
261
261
  <bpmn:extensionElements>
262
262
  <zeebe:taskDefinition type="senior:plan" />
263
+ <zeebe:agentDefinition agentType="external" />
263
264
  <zeebe:linkedResources>
264
265
  <zeebe:linkedResource resourceId="prompts/plan.md" bindingType="latest" resourceType="GenericScript" linkName="prompt" />
265
266
  </zeebe:linkedResources>
@@ -311,6 +312,7 @@
311
312
  <bpmn:serviceTask id="review-plan" name="Review plan (agent)">
312
313
  <bpmn:extensionElements>
313
314
  <zeebe:taskDefinition type="senior:plan-review" />
315
+ <zeebe:agentDefinition agentType="external" />
314
316
  <zeebe:linkedResources>
315
317
  <zeebe:linkedResource resourceId="prompts/plan-review.md" bindingType="latest" resourceType="GenericScript" linkName="prompt" />
316
318
  </zeebe:linkedResources>
@@ -520,6 +522,7 @@
520
522
  <bpmn:serviceTask id="trial-merge" name="Trial merge (agent)">
521
523
  <bpmn:extensionElements>
522
524
  <zeebe:taskDefinition type="senior:trial-merge" />
525
+ <zeebe:agentDefinition agentType="external" />
523
526
  <zeebe:linkedResources>
524
527
  <zeebe:linkedResource resourceId="prompts/trial-merge.md" bindingType="latest" resourceType="GenericScript" linkName="prompt" />
525
528
  </zeebe:linkedResources>
@@ -43,6 +43,7 @@
43
43
  <bpmn:serviceTask id="conformance" name="Verify implementation vs spec (agent)">
44
44
  <bpmn:extensionElements>
45
45
  <zeebe:taskDefinition type="senior:conformance" />
46
+ <zeebe:agentDefinition agentType="external" />
46
47
  <zeebe:linkedResources>
47
48
  <zeebe:linkedResource resourceId="prompts/conformance.md" bindingType="latest" resourceType="GenericScript" linkName="prompt" />
48
49
  </zeebe:linkedResources>
@@ -91,6 +92,7 @@
91
92
  <bpmn:serviceTask id="synthesize" name="Synthesize &#38; promote (agent)">
92
93
  <bpmn:extensionElements>
93
94
  <zeebe:taskDefinition type="senior:retro" />
95
+ <zeebe:agentDefinition agentType="external" />
94
96
  <zeebe:linkedResources>
95
97
  <zeebe:linkedResource resourceId="prompts/retro.md" bindingType="latest" resourceType="GenericScript" linkName="prompt" />
96
98
  </zeebe:linkedResources>
@@ -9,18 +9,19 @@
9
9
  // and linked resources — with a legible red/green diff on mismatch.
10
10
  //
11
11
  // Ported models run a real `assertDerivationParity`; parked models (see
12
- // `./flows.ts`) are reported as skipped WITH their precise reason, in two
12
+ // `./flows.ts`) are reported as skipped WITH their precise reason, in three
13
13
  // blocker classes — class 1: multiple top-level start/end events; class 2:
14
- // arbitrary control-flow graph (`convergence-loop`). Companion diagnostics prove
15
- // each blocker is real against the goldens themselves. No golden is modified to
14
+ // arbitrary control-flow graph (`convergence-loop`); class 3: the engine-native
15
+ // agent-task marker (`retro`, issue #745). Companion diagnostics prove each
16
+ // blocker is real against the goldens themselves. No golden is modified to
16
17
  // force a match — the derivation must reproduce the checked-in file.
17
18
 
18
19
  import { test } from "node:test";
19
20
  import { readFileSync } from "node:fs";
20
21
  import { assert, assertEquals } from "#test-assert";
21
- import { assertDerivationParity, normalize } from "@nanobpm/workflow/test-support";
22
+ import { assertDerivationParity, diffModels, modelsEqual, normalize } from "@nanobpm/workflow/test-support";
22
23
  import { declarativeToBpmn, defineFlow } from "@nanobpm/workflow";
23
- import { PORTS } from "./flows.ts";
24
+ import { PORTS, retroFlow } from "./flows.ts";
24
25
 
25
26
  const ROOT = decodeURIComponent(new URL("../../", import.meta.url).pathname);
26
27
  const goldenPath = (model: string): string => `${ROOT}resources/processes/${model}.bpmn`;
@@ -98,8 +99,9 @@ test("class-1 blocked goldens genuinely have multiple top-level start/end events
98
99
  }
99
100
 
100
101
  // The two single-start/single-end goldens (retro, convergence-loop) clear
101
- // class 1; retro is a GREEN whole-model parity port (it runs through
102
- // `assertDerivationParity` above), convergence-loop is class-2 blocked (below).
102
+ // class 1; each is blocked on a LATER class instead retro on the class-3
103
+ // agent-task marker (proven below), convergence-loop on its class-2 arbitrary
104
+ // graph.
103
105
  for (const model of ["retro", "convergence-loop"]) {
104
106
  const xml = readFileSync(goldenPath(model), "utf8");
105
107
  assertEquals(countTag(xml, "startEvent"), 1, `${model} should have one start event`);
@@ -157,3 +159,38 @@ test("loop() inserts a gateway head, so back-edges cannot merge into a task", ()
157
159
  "the loop-body task cannot itself be the back-edge merge (it stays in<=1)",
158
160
  );
159
161
  });
162
+
163
+ // CLASS 3 — retro has a single top-level start/end (clears class 1) and a fully
164
+ // structured topology (clears class 2), but issue #745 added the engine-native
165
+ // AgentTask marker `<zeebe:agentDefinition agentType="external" />` to its two
166
+ // prompt-bearing `senior:*` tasks and the published compiler cannot emit it.
167
+ // Prove the blocker is real AND that it is the ONLY divergence, so the parked
168
+ // entry is a complete, verified port held ready — not an abandoned one.
169
+ test("retro's complete port differs from its golden by ONLY the agent-task marker", () => {
170
+ const golden = readFileSync(goldenPath("retro"), "utf8");
171
+ const derived = declarativeToBpmn(retroFlow);
172
+ const markers = (xml: string): number =>
173
+ (xml.match(/<(?:\w+:)?agentDefinition\b[^>]*\bagentType="external"/g) ?? []).length;
174
+
175
+ // (a) the golden really carries the marker, on BOTH of its agent tasks — and it
176
+ // cannot simply be dropped: app/agentic/vocab/agent-marker.test.ts is a
177
+ // defect-class guard requiring it on every deployed prompt-bearing agent task.
178
+ assertEquals(markers(golden), 2, "retro's golden should mark senior:conformance and senior:retro");
179
+
180
+ // (b) the published compiler emits none of it — the blocker is real, not a guess.
181
+ // When this starts failing, upstream task() grew marker support: un-park retro
182
+ // by threading `retroFlow` back into its PORTS entry in ./flows.ts.
183
+ assertEquals(markers(derived), 0, "@nanobpm/workflow can now emit <zeebe:agentDefinition/> — un-park retro");
184
+
185
+ // (c) strip exactly the marker lines from the golden and the port derives the
186
+ // WHOLE model green, through the shared harness's own normalize/equality —
187
+ // so nothing but the marker diverges.
188
+ const unmarked = golden.replace(/^[ \t]*<(?:\w+:)?agentDefinition\b[^>]*\/>[ \t]*\r?\n/gm, "");
189
+ assertEquals(markers(unmarked), 0, "the strip must remove every marker line");
190
+ const expected = normalize(unmarked);
191
+ const actual = normalize(derived);
192
+ assert(
193
+ modelsEqual(expected, actual),
194
+ `retro's port must derive its golden once the agent-task marker is stripped — residual drift:\n${diffModels(expected, actual)}`,
195
+ );
196
+ });
@@ -9,10 +9,9 @@
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
- // • `retro` is a GREEN whole-model parity port (see below). The remaining seven
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:
12
+ // • EVERY golden is currently `blockedReason`-parked, in THREE distinct
13
+ // classes, each awaiting an upstream `@nanobpm/workflow` (nano-ide) construct
14
+ // + re-release — never a golden edit and never relaxed acceptance:
16
15
  //
17
16
  // (1) MULTI top-level start/end (spine-demo, readiness-gate, feature,
18
17
  // merge-loop, plan-fanout, delivery-human). `@nanobpm/workflow` derives EXACTLY
@@ -35,6 +34,19 @@
35
34
  // arbitrary-graph / explicit-join (named-target) builder — a SUPERSET of
36
35
  // the class-(1) gap.
37
36
  //
37
+ // (3) ENGINE-NATIVE AGENT-TASK MARKER (retro). `retro` clears BOTH classes
38
+ // above and WAS a green whole-model parity port: its single start/end,
39
+ // linear pipeline, `deviations?` branch, userTask, data envelopes, prompt
40
+ // bindings and general `io` mappings all derive faithfully — the complete
41
+ // port is still authored below, and `derivation-parity.test.ts` proves it
42
+ // differs from its golden by NOTHING but the marker. Issue #745 then added
43
+ // `<zeebe:agentDefinition agentType="external" />` to every prompt-bearing
44
+ // `senior:*` task (a marker `app/agentic/vocab/agent-marker.test.ts`
45
+ // requires, so the golden cannot drop it), and the published compiler
46
+ // cannot emit it: `task()` accepts only `{ jobType, prompt, io }`, and no
47
+ // `agentDefinition`/`agentType` appears anywhere in
48
+ // `@nanobpm/workflow@0.14.0`. Needs an agent-marker option on `task()`.
49
+ //
38
50
  // A resumed run flips any parked model to a real `flow` once the corresponding
39
51
  // upstream construct lands and `@nanobpm/workflow` is bumped to carry it.
40
52
 
@@ -60,7 +72,7 @@ export type PortEntry =
60
72
  readonly blockedReason: string;
61
73
  };
62
74
 
63
- // ── retro (GREENwhole-model parity) ───────────────────────────────────────
75
+ // ── retro (PARKEDclass 3: agent-task marker; the port is otherwise COMPLETE)
64
76
  // retro is a single-start/single-end model: a linear gather → conformance →
65
77
  // record-conformance agent pipeline, a `deviations?` exclusive gateway guarding a
66
78
  // conformance-escalation subgraph (nano-workforce #355/#356), then a shared
@@ -72,6 +84,15 @@ export type PortEntry =
72
84
  // (nano-ide#405) — `w.task`+`io` for `record-conformance-ack`'s general
73
85
  // <zeebe:ioMapping> (inputs `=planKey`→planKey and
74
86
  // `=if (is defined(note)) then note else null`→note).
87
+ //
88
+ // The port below is therefore kept WHOLE and exported, not deleted: it is a
89
+ // faithful whole-model derivation whose ONLY divergence from the golden is the
90
+ // `<zeebe:agentDefinition agentType="external" />` marker issue #745 added to the
91
+ // two `senior:*` tasks (class 3 above). `derivation-parity.test.ts` asserts that
92
+ // divergence is exactly the marker, so the moment upstream `task()` grows an
93
+ // agent-marker option this diagnostic fires and the model is un-parked by
94
+ // threading `retroFlow` back into its PORTS entry — a one-line change, with the
95
+ // port already proven correct rather than re-authored from scratch.
75
96
 
76
97
  /** The typed data envelopes retro's non-agent service tasks lift into the model
77
98
  * (`nano:shape` + `io.nanobpm.dataEnvelope.in`), matching the golden's shapes. */
@@ -96,8 +117,10 @@ const ConformanceRecordIn = envelope("ConformanceRecordIn", {
96
117
  summary: { type: "string", optional: true },
97
118
  });
98
119
 
99
- /** The code-first port of `resources/processes/retro.bpmn`. */
100
- const retroFlow: DeclarativeFlow = defineFlow(
120
+ /** The code-first port of `resources/processes/retro.bpmn` — complete but for the
121
+ * class-3 agent-task marker, so it is exported for the parity diagnostic that
122
+ * pins the divergence to exactly that marker (and parked in PORTS below). */
123
+ export const retroFlow: DeclarativeFlow = defineFlow(
101
124
  "retro",
102
125
  {
103
126
  gather: { in: RetroGatherIn },
@@ -146,9 +169,26 @@ const MULTI_START_END_BLOCK =
146
169
  "cannot reproduce. Awaits an upstream terminal/explicit-end (+ multi-start) " +
147
170
  "construct in @nanobpm/workflow (nano-ide).";
148
171
 
172
+ /** The engine-native AgentTask marker limitation (issue #745), the `blockedReason`
173
+ * for a golden whose ONLY underivable feature is `<zeebe:agentDefinition
174
+ * agentType="external" />` on its agent service tasks. */
175
+ const AGENT_MARKER_BLOCK =
176
+ "blocked (agent-task marker): single top-level start/end and a fully " +
177
+ "structured topology, so this golden derives EXCEPT for the " +
178
+ "<zeebe:agentDefinition agentType=\"external\" /> marker issue #745 added to " +
179
+ "its prompt-bearing senior:* tasks — a marker the published compiler cannot " +
180
+ "emit (task() accepts only { jobType, prompt, io }, and no agentDefinition " +
181
+ "appears anywhere in @nanobpm/workflow@0.14.0). The golden cannot drop the " +
182
+ "marker: app/agentic/vocab/agent-marker.test.ts is a defect-class guard " +
183
+ "requiring it on every deployed prompt-bearing agent task. Proven in the test " +
184
+ "suite — the complete port (retroFlow, still authored here) differs from the " +
185
+ "golden by nothing else. Awaits an agent-marker option on task() upstream in " +
186
+ "@nanobpm/workflow (nano-ide); flip this entry back to `flow: retroFlow` when " +
187
+ "it lands.";
188
+
149
189
  /** All ports, keyed by model, in the epic's stated authoring order. */
150
190
  export const PORTS: readonly PortEntry[] = [
151
- { model: "retro", flow: retroFlow },
191
+ { model: "retro", blockedReason: AGENT_MARKER_BLOCK },
152
192
  {
153
193
  model: "spine-demo",
154
194
  blockedReason: `${MULTI_START_END_BLOCK} (spine-demo: 1 start, 2 ends)`,