@nanobpm/nano-workforce 0.163.1 → 0.163.2

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.163.2](https://github.com/nanobpm/nano-workforce/compare/v0.163.1...v0.163.2) (2026-08-31)
2
+
3
+ ### Build System
4
+
5
+ * **deps:** bump @nanobpm/urban-testkit to ^1.0.0 (native callActivity in e2e) ([#635](https://github.com/nanobpm/nano-workforce/issues/635)) ([2020817](https://github.com/nanobpm/nano-workforce/commit/2020817a2b5baeec347b2c808212720ab58328cb)), closes [#631](https://github.com/nanobpm/nano-workforce/issues/631) [#591](https://github.com/nanobpm/nano-workforce/issues/591) [nano-ide#503](https://github.com/nanobpm/nano-ide/issues/503) [#607](https://github.com/nanobpm/nano-workforce/issues/607) [nano-ide#503](https://github.com/nanobpm/nano-ide/issues/503) [#607](https://github.com/nanobpm/nano-workforce/issues/607) [#503](https://github.com/nanobpm/nano-workforce/issues/503) [#607](https://github.com/nanobpm/nano-workforce/issues/607) [nano-ide#503](https://github.com/nanobpm/nano-ide/issues/503)
6
+
1
7
  ## [0.163.1](https://github.com/nanobpm/nano-workforce/compare/v0.163.0...v0.163.1) (2026-08-30)
2
8
 
3
9
  ### Bug Fixes
@@ -130,7 +130,10 @@ and does **not** resolve `$ref`s, so every projected (non-`x-mcp`) request-body
130
130
  in `openapi.yaml` presents an inline `type: object` body with no `$ref`; the two graph doors
131
131
  additionally carry a worked `example` — an agent discovers the body shape (and calls the tool
132
132
  with a real object, not a
133
- stringified one) from the surface alone. The two graph doors split by convention:
133
+ stringified one) from the surface alone and even if an agent's client emits a
134
+ stringified object body, the door itself now **faithfully parses** it rather than
135
+ rejecting it (nano-ide#503, a server-side input-compatibility behavior that needs no
136
+ client upgrade — see the faithful-transport note below). The two graph doors split by convention:
134
137
  `compileDeliveryGraph` takes the **structured `DeliveryGraph` object** (and *stages*);
135
138
  `previewDeliveryGraph` takes the **text shape `{ "graphJson": "<serialized DeliveryGraph>" }`**
136
139
  (and is *pure*). Every validation failure returns `issues`/`errors` as `[{ path, message }]`.
@@ -138,9 +141,14 @@ The inline bodies are **derived** from `components.schemas` by
138
141
  `scripts/inline-mcp-bodies.ts` (single source of truth; run `npm run gen:mcp-bodies` after
139
142
  editing a component), and `npm run check:mcp-bodies` + `test/mcp-tool-schemas.test.ts` (which
140
143
  runs the real projector) fail CI if a `$ref` ever re-leaks. The upstream projector fix that
141
- would make this mitigation unnecessary is tracked in
144
+ would make the **schema** mitigation unnecessary is tracked in
142
145
  [nano-ide#501](https://github.com/nanobpm/nano-ide/issues/501) (#502 self-contained schemas,
143
- #503 faithful object-body transport, #504 real-spec conformance guard).
146
+ #504 real-spec conformance guard). **#503 faithful object-body transport has landed**
147
+ `@nanobpm/urban` 0.87 ships ADR 0067's `normalizeBodyArg`, so the MCP door now **parses** a
148
+ stringified object body and forwards it faithfully instead of rejecting it with `expected object,
149
+ got string`. That retired the nwf-local stringified-body reject mitigation: the e2e guard in
150
+ `e2e/mcp-surface.e2e.ts` now asserts the door faithfully parses a stringified body (the
151
+ `assertObjectBodyAccepted` detector's teeth stay pinned synthetically).
144
152
 
145
153
  ## 5. Fallback
146
154
 
@@ -178,13 +186,16 @@ depends on:
178
186
 
179
187
  - every projected tool schema is `$ref`-free with an explicit `type` (a leaked
180
188
  `$ref` is unresolvable in the MCP context);
181
- - an object argument arrives **as an object**, never coerced to a string;
189
+ - an object argument arrives **as an object** and a stringified one is faithfully
190
+ **parsed** by the door (ADR 0067 / nano-ide#503, `@nanobpm/urban` ≥ 0.87), never
191
+ rejected as `expected object, got string`;
182
192
  - validation failures answer uniformly with `issues[{path,message}]`;
183
193
  - side-effecting calls stage nothing, so the suite is safe to re-run.
184
194
 
185
195
  It runs in CI under `npm run e2e` (hermetic — no socket, no GitHub), so a
186
- reintroduced `$ref` or a stringified object body fails the build instead of
187
- reaching an agent.
196
+ reintroduced `$ref` fails the build, and a stringified object body is asserted to be
197
+ faithfully parsed by the door (ADR 0067 / nano-ide#503) instead of reaching an agent
198
+ mis-serialized.
188
199
 
189
200
  **Extending it (new per-tool case).** Import `bootMcpHarness` from
190
201
  `e2e/support/mcp-harness.ts` in your own `e2e/<slice>.e2e.ts` and drive
@@ -6,7 +6,8 @@
6
6
  // `e2e/support/mcp-harness.ts` module. It PINS the client-visible contract the S0 defect broke:
7
7
  //
8
8
  // • every projected tool schema is `$ref`-free with an explicit `type` (S0 / nano-ide#502);
9
- // • an object argument arrives AS AN OBJECT, never coerced to a string (S0 / nano-ide#503);
9
+ // • an object argument arrives AS AN OBJECT and a stringified one is faithfully PARSED, not
10
+ // rejected, now that ADR 0067 / nano-ide#503 landed upstream in @nanobpm/urban 0.87;
10
11
  // • validation failures answer uniformly with `issues[{path,message}]`;
11
12
  // • the reads parse; the mutating framework tools are gated; side-effecting calls leave NO live
12
13
  // staged proposal behind (safe to run repeatedly).
@@ -192,9 +193,14 @@ describe("MCP surface e2e — the runtime-served /app/mcp handshake, per tool (S
192
193
  );
193
194
  });
194
195
 
195
- // The falsifiable core (issue #607 acceptance): DELIBERATELY reintroducing either half of the S0
196
+ // The falsifiable core (issue #607 acceptance): DELIBERATELY reintroducing the SCHEMA half of the S0
196
197
  // defect makes the harness fail. These pin the detector's teeth independently of whether S0 has
197
- // landed — so the guard cannot silently rot into a no-op.
198
+ // landed — so the guard cannot silently rot into a no-op. The object-body-stringification half
199
+ // (nano-ide#503) is now fixed UPSTREAM — @nanobpm/urban 0.87 lands ADR 0067 "faithful object-body
200
+ // transport" (`normalizeBodyArg`), so the door PARSES a stringified object body and forwards it
201
+ // faithfully instead of rejecting it. That retired the nwf-local reject mitigation (docs/mcp-runbook.md
202
+ // §4): the live door can no longer produce the S0 signature to exercise end-to-end, so that half is
203
+ // now covered by a live faithful-parse assertion plus a synthetic detector-teeth check below.
198
204
  describe("reintroducing the S0 defect fails the build", () => {
199
205
  test("a $ref in a tool schema is caught by the self-containment assertion", () => {
200
206
  const good = { type: "object", properties: { body: { type: "object", properties: { n: { type: "number" } } } } };
@@ -208,17 +214,42 @@ describe("MCP surface e2e — the runtime-served /app/mcp handshake, per tool (S
208
214
  assert.throws(() => assertSchemaSelfContained(typeless, "synthetic-typeless"), /type/, "a typeless schema must throw");
209
215
  });
210
216
 
211
- test("a stringified object body is rejected by the door and caught by assertObjectBodyAccepted", async () => {
212
- // Simulate the S0 client coercion: send the body as a JSON STRING instead of an object.
213
- const res = await h.callTool("compileDeliveryGraph", { body: JSON.stringify(MINIMAL_VALID_GRAPH) });
214
- assert.ok(res.isError, "a stringified object body must be rejected by the door");
217
+ test("a stringified object body is faithfully parsed by the door (ADR 0067 / nano-ide#503)", async () => {
218
+ // Simulate the old S0 client coercion: send the body as a JSON STRING instead of an object.
219
+ // @nanobpm/urban 0.87's faithful object-body transport (ADR 0067 `normalizeBodyArg`) now PARSES
220
+ // it and forwards it faithfully no longer the "expected object, got string" rejection the
221
+ // nwf-local S0 mitigation used to raise. Drive the PURE previewDeliveryGraph door so the
222
+ // parsed-and-compiled graph stages nothing.
223
+ const res = await h.callTool("previewDeliveryGraph", {
224
+ body: JSON.stringify({ graphJson: JSON.stringify(MINIMAL_VALID_GRAPH) }),
225
+ });
226
+ assert.ok(!res.isError, `the door must faithfully parse a stringified object body: ${res.text}`);
215
227
  assert.ok(
216
- res.text.includes(STRINGIFIED_BODY_MESSAGE),
217
- `the door must report "${STRINGIFIED_BODY_MESSAGE}": ${res.text}`,
228
+ !res.text.includes(STRINGIFIED_BODY_MESSAGE),
229
+ `faithful transport must not reject with "${STRINGIFIED_BODY_MESSAGE}": ${res.text}`,
218
230
  );
219
- // The harness's guard must recognize that signature as a failure.
231
+ assert.doesNotThrow(
232
+ () => assertObjectBodyAccepted(res, "previewDeliveryGraph"),
233
+ "a faithfully parsed object body must pass assertObjectBodyAccepted",
234
+ );
235
+ const json = res.json as { ok?: boolean; staged?: boolean } | undefined;
236
+ assert.equal(json?.ok, true, `previewDeliveryGraph must compile the parsed graph: ${res.text}`);
237
+ assert.equal(json?.staged, false, "previewDeliveryGraph is PURE — it must never stage");
238
+ });
239
+
240
+ test("assertObjectBodyAccepted still flags an S0 stringified-body signature (detector teeth)", () => {
241
+ // The live door can no longer produce the S0 signature (fixed upstream, ADR 0067), so pin the
242
+ // detector's teeth SYNTHETICALLY — mirroring the $ref/typeless guards above — so the helper
243
+ // cannot rot into a no-op if the signature ever re-surfaces from another surface.
244
+ const s0Result = {
245
+ isError: true,
246
+ text: `validation failed: body: ${STRINGIFIED_BODY_MESSAGE}`,
247
+ json: undefined,
248
+ httpStatus: 422,
249
+ raw: undefined,
250
+ };
220
251
  assert.throws(
221
- () => assertObjectBodyAccepted(res, "compileDeliveryGraph"),
252
+ () => assertObjectBodyAccepted(s0Result, "synthetic-stringified"),
222
253
  /stringified/,
223
254
  "assertObjectBodyAccepted must flag a stringified-body result",
224
255
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanobpm/nano-workforce",
3
- "version": "0.163.1",
3
+ "version": "0.163.2",
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",
@@ -61,12 +61,12 @@
61
61
  },
62
62
  "dependencies": {
63
63
  "@nanobpm/agentic": "^0.4.0",
64
- "@nanobpm/urban": "^0.86.0",
64
+ "@nanobpm/urban": "^0.87.0",
65
65
  "bpmn-auto-layout": "^2.0.0-alpha.2"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@biomejs/biome": "^2.4.11",
69
- "@nanobpm/urban-testkit": "^0.14.0",
69
+ "@nanobpm/urban-testkit": "^1.0.0",
70
70
  "@nanobpm/workflow": "^0.14.0",
71
71
  "@semantic-release/changelog": "^7.0.0",
72
72
  "@semantic-release/git": "^11.0.0",