@nanobpm/nano-workforce 0.117.0 → 0.118.1

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.
@@ -0,0 +1,153 @@
1
+ {
2
+ "schemaVersion": "1.0",
3
+ "title": "Delivery Graphs",
4
+ "nodes": [
5
+ {
6
+ "type": "nav",
7
+ "id": "nav",
8
+ "props": {
9
+ "variant": "bar",
10
+ "title": "Nano Workforce",
11
+ "items": [
12
+ {
13
+ "label": "Overview",
14
+ "page": "overview"
15
+ },
16
+ {
17
+ "label": "Lineage",
18
+ "page": "lineage"
19
+ },
20
+ {
21
+ "label": "Convergence",
22
+ "page": "home"
23
+ },
24
+ {
25
+ "label": "Epics",
26
+ "page": "epic"
27
+ },
28
+ {
29
+ "label": "Feature",
30
+ "page": "feature"
31
+ },
32
+ {
33
+ "label": "Delivery Graphs",
34
+ "page": "delivery-graphs"
35
+ },
36
+ {
37
+ "label": "Tasks",
38
+ "page": "tasks",
39
+ "badge": {
40
+ "source": "app",
41
+ "table": "user_tasks",
42
+ "filter": [],
43
+ "tone": "danger",
44
+ "refreshMs": 5000,
45
+ "hideWhenZero": true
46
+ }
47
+ },
48
+ {
49
+ "label": "Cockpit",
50
+ "page": "cockpit"
51
+ },
52
+ {
53
+ "label": "Board",
54
+ "page": "board"
55
+ },
56
+ {
57
+ "label": "Velocity",
58
+ "page": "velocity"
59
+ }
60
+ ],
61
+ "sticky": true
62
+ }
63
+ },
64
+ {
65
+ "type": "text",
66
+ "id": "title",
67
+ "props": { "text": "Delivery Graphs", "variant": "heading" }
68
+ },
69
+ {
70
+ "type": "text",
71
+ "id": "subtitle",
72
+ "props": {
73
+ "text": "The human front door for delivery graphs (ADR 0005). Paste an agent-authored delivery-graph JSON, Preview it (a pure compile \u2014 nothing is dispatched) to see it validates and read its content digest, then Dispatch it. A graph with any side-effecting node (it merges PRs / publishes packages) dispatches only when you tick Approve; without approval it is parked in the in-flight grid below for review. Preview and Dispatch are thin UIs over the same compileDeliveryGraph / startDeliveryGraph doors \u2014 there is no parallel submit path.",
74
+ "variant": "sub"
75
+ }
76
+ },
77
+ {
78
+ "type": "actionForm",
79
+ "id": "delivery-graph-preview",
80
+ "props": {
81
+ "title": "1 \u00b7 Preview (compile \u2014 pure, nothing is dispatched)",
82
+ "submitLabel": "Preview",
83
+ "action": {
84
+ "path": "/app/api/actions/delivery-graph/preview",
85
+ "body": "{{form}}",
86
+ "successLabel": "\u2713 Valid \u2014 the graph compiled. Nothing was dispatched. Review its shape in the in-flight grid, then Dispatch it below."
87
+ },
88
+ "fields": [
89
+ { "key": "graphJson", "label": "Delivery-graph JSON \u2014 paste the agent-authored DeliveryGraph (nodes/edges). Preview validates + compiles it without dispatching.", "type": "text", "required": true, "requiredMessage": "Paste a delivery-graph JSON to preview" }
90
+ ]
91
+ }
92
+ },
93
+ {
94
+ "type": "actionForm",
95
+ "id": "delivery-graph-dispatch",
96
+ "props": {
97
+ "title": "2 \u00b7 Dispatch (start \u2014 gated, idempotent)",
98
+ "submitLabel": "Dispatch",
99
+ "action": {
100
+ "path": "/app/api/actions/delivery-graph/dispatch",
101
+ "body": "{{form}}",
102
+ "successLabel": "\u2713 Dispatched \u2014 watch it advance in the in-flight grid below."
103
+ },
104
+ "fields": [
105
+ { "key": "graphJson", "label": "Delivery-graph JSON \u2014 paste the same DeliveryGraph you previewed.", "type": "text", "required": true, "requiredMessage": "Paste the delivery-graph JSON to dispatch" },
106
+ { "key": "approve", "label": "Approve \u2014 I reviewed the preview and approve dispatching this graph's side effects (required for any agent/connector node)", "type": "checkbox" },
107
+ { "key": "idempotencyKey", "label": "Idempotency key (optional) \u2014 a re-dispatch with the same key (or, blank, the same graph) will not double-launch an in-flight run", "type": "text" }
108
+ ]
109
+ }
110
+ },
111
+ {
112
+ "type": "dataGrid",
113
+ "id": "delivery-graphs-inflight",
114
+ "props": {
115
+ "title": "In-flight delivery graphs",
116
+ "collapsible": true,
117
+ "defaultCollapsed": false,
118
+ "showCount": true,
119
+ "rowKey": "run_key",
120
+ "refreshMs": 5000,
121
+ "empty": "No delivery graphs in flight. Preview and Dispatch one above.",
122
+ "data": {
123
+ "kind": "datasource",
124
+ "source": "app",
125
+ "table": "delivery_graph_runs",
126
+ "orderBy": { "field": "updated_at", "dir": "desc" },
127
+ "filter": [{ "field": "status", "in": ["awaiting-approval", "running"] }]
128
+ },
129
+ "tabs": [
130
+ { "label": "In-flight", "filter": [{ "field": "status", "in": ["awaiting-approval", "running"] }] },
131
+ { "label": "History", "filter": [{ "field": "status", "in": ["done", "failed", "abandoned"] }] },
132
+ { "label": "All", "filter": [] }
133
+ ],
134
+ "columns": [
135
+ { "field": "title", "template": "{{title}}", "header": "Graph", "subtitleField": "run_key", "truncate": true, "width": "28%", "link": { "kind": "page", "page": "delivery-graph-detail", "keyField": "run_key" } },
136
+ { "field": "status", "header": "Status", "link": { "kind": "processExplorer", "keyField": "process_key" } },
137
+ { "field": "phase", "header": "Phase", "truncate": true, "width": "26%" },
138
+ { "field": "node_count", "header": "Nodes" },
139
+ { "field": "human_node_count", "header": "Human" },
140
+ { "field": "side_effect_count", "header": "Side effects" },
141
+ { "field": "updated_at", "header": "Updated", "width": "9rem", "format": "datetime" }
142
+ ],
143
+ "detail": {
144
+ "fields": [
145
+ { "field": "process_definition_id", "label": "Definition" },
146
+ { "field": "digest", "label": "Digest (approval token)" },
147
+ { "field": "phase_node_id", "label": "Parked node" }
148
+ ]
149
+ }
150
+ }
151
+ }
152
+ ]
153
+ }
@@ -29,6 +29,10 @@
29
29
  "label": "Feature",
30
30
  "page": "feature"
31
31
  },
32
+ {
33
+ "label": "Delivery Graphs",
34
+ "page": "delivery-graphs"
35
+ },
32
36
  {
33
37
  "label": "Tasks",
34
38
  "page": "tasks",
@@ -29,6 +29,10 @@
29
29
  "label": "Feature",
30
30
  "page": "feature"
31
31
  },
32
+ {
33
+ "label": "Delivery Graphs",
34
+ "page": "delivery-graphs"
35
+ },
32
36
  {
33
37
  "label": "Tasks",
34
38
  "page": "tasks",
@@ -29,6 +29,10 @@
29
29
  "label": "Feature",
30
30
  "page": "feature"
31
31
  },
32
+ {
33
+ "label": "Delivery Graphs",
34
+ "page": "delivery-graphs"
35
+ },
32
36
  {
33
37
  "label": "Tasks",
34
38
  "page": "tasks",
@@ -29,6 +29,10 @@
29
29
  "label": "Feature",
30
30
  "page": "feature"
31
31
  },
32
+ {
33
+ "label": "Delivery Graphs",
34
+ "page": "delivery-graphs"
35
+ },
32
36
  {
33
37
  "label": "Tasks",
34
38
  "page": "tasks",
@@ -29,6 +29,10 @@
29
29
  "label": "Feature",
30
30
  "page": "feature"
31
31
  },
32
+ {
33
+ "label": "Delivery Graphs",
34
+ "page": "delivery-graphs"
35
+ },
32
36
  {
33
37
  "label": "Tasks",
34
38
  "page": "tasks",
@@ -29,6 +29,10 @@
29
29
  "label": "Feature",
30
30
  "page": "feature"
31
31
  },
32
+ {
33
+ "label": "Delivery Graphs",
34
+ "page": "delivery-graphs"
35
+ },
32
36
  {
33
37
  "label": "Tasks",
34
38
  "page": "tasks",
@@ -66,7 +70,7 @@
66
70
  "type": "text",
67
71
  "id": "subtitle",
68
72
  "props": {
69
- "text": "Everything currently in flight across the three dispatch surfaces \u2014 PR convergences, Epics, and Feature runs. Each section lists only its active items, remembers whether you collapsed it, and shows a live count in its header. Follow any row to its detail page.",
73
+ "text": "Everything currently in flight across the four dispatch surfaces \u2014 PR convergences, Epics, Feature runs, and Delivery Graphs. Each section lists only its active items, remembers whether you collapsed it, and shows a live count in its header. Follow any row to its detail page.",
70
74
  "variant": "sub"
71
75
  }
72
76
  },
@@ -208,7 +212,7 @@
208
212
  "filter": [{ "field": "status", "in": ["awaiting-approval", "running"] }]
209
213
  },
210
214
  "columns": [
211
- { "field": "title", "template": "{{title}}", "header": "Item", "subtitleField": "run_key", "truncate": true, "width": "30%" },
215
+ { "field": "title", "template": "{{title}}", "header": "Item", "subtitleField": "run_key", "truncate": true, "width": "30%", "link": { "kind": "page", "page": "delivery-graph-detail", "keyField": "run_key" } },
212
216
  { "field": "status", "header": "Status", "link": { "kind": "processExplorer", "keyField": "process_key" } },
213
217
  { "field": "phase", "header": "Phase", "truncate": true, "width": "28%" },
214
218
  { "field": "updated_at", "header": "Updated", "width": "9rem", "format": "datetime" }
@@ -29,6 +29,10 @@
29
29
  "label": "Feature",
30
30
  "page": "feature"
31
31
  },
32
+ {
33
+ "label": "Delivery Graphs",
34
+ "page": "delivery-graphs"
35
+ },
32
36
  {
33
37
  "label": "Tasks",
34
38
  "page": "tasks",
@@ -29,6 +29,10 @@
29
29
  "label": "Feature",
30
30
  "page": "feature"
31
31
  },
32
+ {
33
+ "label": "Delivery Graphs",
34
+ "page": "delivery-graphs"
35
+ },
32
36
  {
33
37
  "label": "Tasks",
34
38
  "page": "tasks",
@@ -282,6 +282,9 @@ test("issue #205: overview is the landing page and first nav item", async () =>
282
282
  },
283
283
  plans: { field: "list_bucket", in: ["active"] },
284
284
  feature_runs: { field: "status", in: ["running", "escalated", "awaiting_operator"] },
285
+ // The 4th dispatch surface (issue #386) — active delivery graphs. Both in-flight statuses
286
+ // (`awaiting-approval` parked at the gate, `running` dispatched) show here.
287
+ delivery_graph_runs: { field: "status", in: ["awaiting-approval", "running"] },
285
288
  };
286
289
  const grids = (overview.nodes ?? []).filter((n: Json) => n.type === "dataGrid");
287
290
  for (const [table, { field, in: values }] of Object.entries(expected)) {
@@ -298,3 +301,67 @@ test("issue #205: overview is the landing page and first nav item", async () =>
298
301
  }
299
302
  });
300
303
 
304
+
305
+ test("issue #386: the human-facing Delivery Graphs surface is wired (nav tab, page, detail, overview)", async () => {
306
+ // 1) A `Delivery Graphs` nav tab in the single-source nav, pointing at the delivery-graphs page.
307
+ const nav = JSON.parse(readFileSync(`${ROOT}pages/_nav.json`, "utf8"));
308
+ const tab = (nav.props?.items ?? []).find(
309
+ (i: Json) => i.page === "delivery-graphs" && i.label === "Delivery Graphs",
310
+ );
311
+ assert(tab, "pages/_nav.json must carry a `Delivery Graphs` nav tab → the delivery-graphs page");
312
+
313
+ // 2) The Submit page exists with a Preview (compile) action and a Dispatch (start) action, plus an
314
+ // in-flight grid over the delivery_graph_runs aggregate that links to the per-graph detail page.
315
+ const page = JSON.parse(readFileSync(`${ROOT}pages/delivery-graphs.page.json`, "utf8"));
316
+ const forms = (page.nodes ?? []).filter((n: Json) => n.type === "actionForm");
317
+ const preview = forms.find((f: Json) => f.props?.action?.path === "/app/api/actions/delivery-graph/preview");
318
+ const dispatch = forms.find((f: Json) => f.props?.action?.path === "/app/api/actions/delivery-graph/dispatch");
319
+ assert(preview, "delivery-graphs page must have a Preview form posting to /app/api/actions/delivery-graph/preview");
320
+ assert(dispatch, "delivery-graphs page must have a Dispatch form posting to /app/api/actions/delivery-graph/dispatch");
321
+ // Both forms take the pasted graph JSON; Dispatch also carries the explicit approve gate.
322
+ assert(
323
+ (preview.props?.fields ?? []).some((fl: Json) => fl.key === "graphJson"),
324
+ "the Preview form must have a graphJson paste field",
325
+ );
326
+ assert(
327
+ (dispatch.props?.fields ?? []).some((fl: Json) => fl.key === "graphJson") &&
328
+ (dispatch.props?.fields ?? []).some((fl: Json) => fl.key === "approve"),
329
+ "the Dispatch form must have a graphJson paste field and an approve gate",
330
+ );
331
+ const grid = (page.nodes ?? []).find(
332
+ (n: Json) => n.type === "dataGrid" && n.props?.data?.table === "delivery_graph_runs",
333
+ );
334
+ assert(grid, "delivery-graphs page must have an in-flight grid over delivery_graph_runs");
335
+ const linkCol = (grid.props?.columns ?? []).find((c: Json) => c.link?.page === "delivery-graph-detail");
336
+ assert(
337
+ linkCol && linkCol.link?.keyField === "run_key",
338
+ "the in-flight grid must link a column to delivery-graph-detail by run_key",
339
+ );
340
+
341
+ // 3) The per-graph detail page reads the run aggregate scoped to the route param (run_key).
342
+ const detail = JSON.parse(readFileSync(`${ROOT}pages/delivery-graph-detail.page.json`, "utf8"));
343
+ const runGrid = (detail.nodes ?? []).find(
344
+ (n: Json) => n.type === "dataGrid" && n.props?.data?.table === "delivery_graph_runs",
345
+ );
346
+ assert(runGrid, "delivery-graph-detail must bind a grid to delivery_graph_runs");
347
+ assert(
348
+ (runGrid.props?.data?.filter ?? []).some((fl: Json) => fl.field === "run_key" && fl.eqParam === true),
349
+ "delivery-graph-detail must scope its run grid to the route param (run_key eqParam)",
350
+ );
351
+
352
+ // 4) The Overview no longer claims only three surfaces, and its 4th section links to the detail page.
353
+ const overview = JSON.parse(readFileSync(`${ROOT}pages/overview.page.json`, "utf8"));
354
+ const subtitle = (overview.nodes ?? []).find((n: Json) => n.id === "subtitle");
355
+ assert(
356
+ typeof subtitle?.props?.text === "string" && !subtitle.props.text.includes("three dispatch surfaces"),
357
+ "overview subtitle must no longer say 'three dispatch surfaces' (a delivery graph is a 4th)",
358
+ );
359
+ const ovGrid = (overview.nodes ?? []).find(
360
+ (n: Json) => n.type === "dataGrid" && n.props?.data?.table === "delivery_graph_runs",
361
+ );
362
+ const ovLink = (ovGrid?.props?.columns ?? []).find((c: Json) => c.link?.page === "delivery-graph-detail");
363
+ assert(
364
+ ovLink && ovLink.link?.keyField === "run_key",
365
+ "overview delivery-graphs section must link its item to delivery-graph-detail by run_key",
366
+ );
367
+ });