@nanobpm/urban 0.72.0 → 0.72.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.
@@ -85,11 +85,19 @@ export async function resolveInstalledConnectors(ctx) {
85
85
  * error boundary by the engine adapter); `job.fail(msg)` a plain throw (retry). */
86
86
  export function adaptConnectorHandler(worker) {
87
87
  return async (job) => {
88
+ // A connector job always belongs to a process instance; a missing key is a
89
+ // protocol fault. Fail loud (the engine retries / raises an incident) rather
90
+ // than handing the connector a fabricated "" that would silently corrupt any
91
+ // instance-scoped call it makes.
92
+ const processInstanceKey = job.processInstanceKey;
93
+ if (processInstanceKey === undefined || processInstanceKey === "") {
94
+ throw new Error(`connector job '${job.jobType}' (${job.jobKey}) has no processInstanceKey`);
95
+ }
88
96
  let outcome;
89
97
  const facade = {
90
98
  jobKey: job.jobKey,
91
99
  type: job.jobType,
92
- processInstanceKey: job.processInstanceKey ?? "",
100
+ processInstanceKey,
93
101
  elementId: job.elementId,
94
102
  variables: job.variables,
95
103
  async complete(vars) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanobpm/urban",
3
- "version": "0.72.0",
3
+ "version": "0.72.1",
4
4
  "description": "Urban: build and run code-first apps on Nano — runtime, derivation toolkit, and CLI in one. Author durable processes with @nanobpm/workflow (defineFlow), a typed datasource, forms, triggers and surfaces from a nano.app.json manifest, on Node or Deno.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -145,6 +145,7 @@ test("mountConnectors imports the pack entry and registers the drained worker",
145
145
  const out = await engine.deliver("slack:send-message", {
146
146
  jobKey: "j1",
147
147
  jobType: "slack:send-message",
148
+ processInstanceKey: "pi1",
148
149
  variables: { text: "hi" },
149
150
  });
150
151
  assert.deepEqual(out, { echoed: "hi" });
@@ -214,7 +215,7 @@ test("mountConnectors fails closed on a duplicate worker type in one pack entry"
214
215
  });
215
216
 
216
217
  test("adaptConnectorHandler maps complete/fail/error(BpmnError) and a returned value", async () => {
217
- const job: EngineJob = { jobKey: "j", jobType: "t", variables: {} };
218
+ const job: EngineJob = { jobKey: "j", jobType: "t", processInstanceKey: "pi", variables: {} };
218
219
 
219
220
  const completes = adaptConnectorHandler({ type: "t", handle: async (j) => void (await j.complete({ a: 1 })) });
220
221
  assert.deepEqual(await completes(job), { a: 1 });
@@ -231,3 +232,22 @@ test("adaptConnectorHandler maps complete/fail/error(BpmnError) and a returned v
231
232
  (e: unknown) => e instanceof BpmnError && e.errorCode === "CODE",
232
233
  );
233
234
  });
235
+
236
+ test("adaptConnectorHandler fails loud on a job with no processInstanceKey (no masking to '')", async () => {
237
+ // Guard for the decode-boundary defect class (Magikcraft/nano-bpm#940): a
238
+ // keyless job must reject, never invoke the connector with a fabricated "".
239
+ let handlerRan = false;
240
+ const h = adaptConnectorHandler({
241
+ type: "t",
242
+ handle: async () => {
243
+ handlerRan = true;
244
+ },
245
+ });
246
+ const keyless: EngineJob = { jobKey: "j", jobType: "t", variables: {} };
247
+ await assert.rejects(() => Promise.resolve(h(keyless)), /no processInstanceKey/);
248
+ assert.equal(handlerRan, false, "connector handler must not run for a keyless job");
249
+
250
+ // An empty-string key is the exact masking source and is rejected too.
251
+ const emptyKey: EngineJob = { jobKey: "j", jobType: "t", processInstanceKey: "", variables: {} };
252
+ await assert.rejects(() => Promise.resolve(h(emptyKey)), /no processInstanceKey/);
253
+ });
@@ -134,6 +134,14 @@ export async function resolveInstalledConnectors(
134
134
  * error boundary by the engine adapter); `job.fail(msg)` a plain throw (retry). */
135
135
  export function adaptConnectorHandler(worker: DefinedConnectorWorker): JobHandler {
136
136
  return async (job: EngineJob) => {
137
+ // A connector job always belongs to a process instance; a missing key is a
138
+ // protocol fault. Fail loud (the engine retries / raises an incident) rather
139
+ // than handing the connector a fabricated "" that would silently corrupt any
140
+ // instance-scoped call it makes.
141
+ const processInstanceKey = job.processInstanceKey;
142
+ if (processInstanceKey === undefined || processInstanceKey === "") {
143
+ throw new Error(`connector job '${job.jobType}' (${job.jobKey}) has no processInstanceKey`);
144
+ }
137
145
  let outcome:
138
146
  | { kind: "complete"; vars?: Record<string, unknown> }
139
147
  | { kind: "fail"; message: string }
@@ -142,7 +150,7 @@ export function adaptConnectorHandler(worker: DefinedConnectorWorker): JobHandle
142
150
  const facade: ConnectorWorkerJob = {
143
151
  jobKey: job.jobKey,
144
152
  type: job.jobType,
145
- processInstanceKey: job.processInstanceKey ?? "",
153
+ processInstanceKey,
146
154
  elementId: job.elementId,
147
155
  variables: job.variables,
148
156
  async complete(vars) {