@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 +6 -0
- package/app/agentic/vocab/agent-marker.test.ts +74 -0
- package/app/agentic/vocab/job-types.ts +26 -0
- package/app/contracts.ts +8 -0
- package/package.json +3 -2
- package/resources/processes/convergence-loop.bpmn +2 -0
- package/resources/processes/implement-cell.bpmn +1 -0
- package/resources/processes/merge-cell.bpmn +1 -0
- package/resources/processes/merge-loop.bpmn +2 -0
- package/resources/processes/plan-fanout.bpmn +3 -0
- package/resources/processes/retro.bpmn +2 -0
- package/test/derivation-parity/derivation-parity.test.ts +44 -7
- package/test/derivation-parity/flows.ts +48 -8
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.
|
|
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.
|
|
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 & 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
|
|
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`)
|
|
15
|
-
//
|
|
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;
|
|
102
|
-
//
|
|
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
|
-
// •
|
|
13
|
-
//
|
|
14
|
-
//
|
|
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 (
|
|
75
|
+
// ── retro (PARKED — class 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
|
-
|
|
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",
|
|
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)`,
|