@voyantjs/workflows-orchestrator-cloudflare 0.107.4 → 0.107.6

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.
@@ -1 +1 @@
1
- {"version":3,"file":"cf-container-runner.d.ts","sourceRoot":"","sources":["../src/cf-container-runner.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAoB,UAAU,EAAE,MAAM,6BAA6B,CAAA;AAE/E;;;;;GAKG;AACH,MAAM,WAAW,sBAAsB;IACrC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,QAAQ,IAAI,MAAM,CAAA;KAAE,CAAA;IAChD,GAAG,CAAC,EAAE,EAAE;QAAE,QAAQ,IAAI,MAAM,CAAA;KAAE,GAAG;QAAE,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;KAAE,CAAA;CAChF;AAED,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAA;IACX;;;;;;OAMG;IACH,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,SAAS,EAAE,sBAAsB,CAAA;IACjC;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE;QACrB,KAAK,EAAE,MAAM,CAAA;QACb,UAAU,EAAE,MAAM,CAAA;QAClB,eAAe,EAAE,MAAM,CAAA;QACvB,SAAS,EAAE,MAAM,CAAA;QACjB,cAAc,EAAE,MAAM,CAAA;KACvB,KAAK,OAAO,CAAC,cAAc,CAAC,GAAG,cAAc,CAAA;IAC9C;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;OAIG;IACH,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAA;IACjD,kCAAkC;IAClC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/E;;;;;OAKG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE;QACnB,KAAK,EAAE,MAAM,CAAA;QACb,UAAU,EAAE,MAAM,CAAA;QAClB,eAAe,EAAE,MAAM,CAAA;QACvB,MAAM,EAAE,MAAM,CAAA;QACd,OAAO,EAAE,MAAM,CAAA;KAChB,KAAK,MAAM,CAAA;CACb;AA2BD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,qBAAqB,GAAG,UAAU,CA2HnF"}
1
+ {"version":3,"file":"cf-container-runner.d.ts","sourceRoot":"","sources":["../src/cf-container-runner.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAoB,UAAU,EAAE,MAAM,6BAA6B,CAAA;AAI/E;;;;;GAKG;AACH,MAAM,WAAW,sBAAsB;IACrC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,QAAQ,IAAI,MAAM,CAAA;KAAE,CAAA;IAChD,GAAG,CAAC,EAAE,EAAE;QAAE,QAAQ,IAAI,MAAM,CAAA;KAAE,GAAG;QAAE,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;KAAE,CAAA;CAChF;AAED,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAA;IACX;;;;;;OAMG;IACH,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,SAAS,EAAE,sBAAsB,CAAA;IACjC;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE;QACrB,KAAK,EAAE,MAAM,CAAA;QACb,UAAU,EAAE,MAAM,CAAA;QAClB,eAAe,EAAE,MAAM,CAAA;QACvB,SAAS,EAAE,MAAM,CAAA;QACjB,cAAc,EAAE,MAAM,CAAA;KACvB,KAAK,OAAO,CAAC,cAAc,CAAC,GAAG,cAAc,CAAA;IAC9C;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;OAIG;IACH,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAA;IACjD,kCAAkC;IAClC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/E;;;;;OAKG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE;QACnB,KAAK,EAAE,MAAM,CAAA;QACb,UAAU,EAAE,MAAM,CAAA;QAClB,eAAe,EAAE,MAAM,CAAA;QACvB,MAAM,EAAE,MAAM,CAAA;QACd,OAAO,EAAE,MAAM,CAAA;KAChB,KAAK,MAAM,CAAA;CACb;AA2BD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,qBAAqB,GAAG,UAAU,CAuInF"}
@@ -23,6 +23,7 @@
23
23
  // `@voyantjs/workflows/handler`'s `executeWorkflowStep` with the
24
24
  // workflow registry already loaded. See
25
25
  // `apps/workflows-node-step-container/` for the reference image.
26
+ const STEP_RESPONSE_AUTH_HEADER = "x-voyant-step-response-auth";
26
27
  /**
27
28
  * Build a `StepRunner` that dispatches each step invocation to a
28
29
  * Cloudflare Container in the given namespace.
@@ -124,6 +125,13 @@ export function createCfContainerStepRunner(deps) {
124
125
  });
125
126
  return failed(attempt, startedAt, "CONTAINER_HTTP_ERROR", new Error(`container returned HTTP ${response.status}: ${text}`));
126
127
  }
128
+ if (deps.sign) {
129
+ const signature = response.headers.get(STEP_RESPONSE_AUTH_HEADER);
130
+ const expected = await deps.sign(text);
131
+ if (!signature || !constantTimeEquals(signature, expected)) {
132
+ return failed(attempt, startedAt, "CONTAINER_RESPONSE_SIGNATURE_INVALID", new Error("container response signature is missing or invalid"));
133
+ }
134
+ }
127
135
  try {
128
136
  const entry = JSON.parse(text);
129
137
  // Trust the container's own timestamps; they reflect the actual
@@ -135,6 +143,14 @@ export function createCfContainerStepRunner(deps) {
135
143
  }
136
144
  };
137
145
  }
146
+ function constantTimeEquals(a, b) {
147
+ const length = Math.max(a.length, b.length, 1);
148
+ let diff = a.length === b.length ? 0 : 1;
149
+ for (let i = 0; i < length; i++) {
150
+ diff |= (a.charCodeAt(i) | 0) ^ (b.charCodeAt(i) | 0);
151
+ }
152
+ return diff === 0;
153
+ }
138
154
  function failed(attempt, startedAt, code, err) {
139
155
  const e = err instanceof Error ? err : new Error(String(err));
140
156
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"cloudflare-edge-driver.d.ts","sourceRoot":"","sources":["../src/cloudflare-edge-driver.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EACV,eAAe,EAOhB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,KAAK,EACV,aAAa,EAOd,MAAM,4BAA4B,CAAA;AAUnC,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,wBAAwB,CAAA;AAE/B,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAA;AAI7D,MAAM,WAAW,2BAA2B;IAC1C,uDAAuD;IACvD,qBAAqB,EAAE,0BAA0B,CAAA;IACjD,uFAAuF;IACvF,oBAAoB,CAAC,EAAE,0BAA0B,CAAA;IACjD,iDAAiD;IACjD,UAAU,EAAE,eAAe,CAAA;IAC3B;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,yEAAyE;IACzE,kBAAkB,CAAC,EAAE,eAAe,CAAA;IACpC,uFAAuF;IACvF,UAAU,CAAC,EAAE;QACX,QAAQ,EAAE,MAAM,CAAA;QAChB,SAAS,EAAE,MAAM,CAAA;QACjB,cAAc,EAAE,MAAM,CAAA;KACvB,CAAA;IACD,8CAA8C;IAC9C,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,MAAM,CAAA;IAC1B,sEAAsE;IACtE,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CAChF;AAUD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,2BAA2B,GAAG,aAAa,CAqa3F"}
1
+ {"version":3,"file":"cloudflare-edge-driver.d.ts","sourceRoot":"","sources":["../src/cloudflare-edge-driver.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EACV,eAAe,EAOhB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,KAAK,EACV,aAAa,EAOd,MAAM,4BAA4B,CAAA;AAUnC,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,wBAAwB,CAAA;AAE/B,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAA;AAI7D,MAAM,WAAW,2BAA2B;IAC1C,uDAAuD;IACvD,qBAAqB,EAAE,0BAA0B,CAAA;IACjD,uFAAuF;IACvF,oBAAoB,CAAC,EAAE,0BAA0B,CAAA;IACjD,iDAAiD;IACjD,UAAU,EAAE,eAAe,CAAA;IAC3B;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,yEAAyE;IACzE,kBAAkB,CAAC,EAAE,eAAe,CAAA;IACpC,uFAAuF;IACvF,UAAU,CAAC,EAAE;QACX,QAAQ,EAAE,MAAM,CAAA;QAChB,SAAS,EAAE,MAAM,CAAA;QACjB,cAAc,EAAE,MAAM,CAAA;KACvB,CAAA;IACD,8CAA8C;IAC9C,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,MAAM,CAAA;IAC1B,sEAAsE;IACtE,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CAChF;AAiED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,2BAA2B,GAAG,aAAa,CAqa3F"}
@@ -1,4 +1,5 @@
1
1
  // Mode 1 driver — Cloudflare edge composition.
2
+ // agent-quality: file-size exception -- Public edge driver factory currently owns manifest, trigger, event-ingest, concurrency, and admin wiring; split only with a dedicated driver-surface refactor.
2
3
  //
3
4
  // `createApp({ workflows: { driver: createCloudflareEdgeDriver({ ... }) } })`
4
5
  // is the entry point for any deployment that runs the orchestrator on
@@ -24,6 +25,42 @@ const DEFAULT_TENANT_META = {
24
25
  projectId: "default",
25
26
  organizationId: "default",
26
27
  };
28
+ function serializeWorkflowManifest(manifest) {
29
+ return { ...manifest };
30
+ }
31
+ function deserializeWorkflowManifest(manifest) {
32
+ const { schemaVersion, projectId, versionId, builtAt, builderVersion, capabilities, workflows, eventFilters, bindings, environments, } = manifest;
33
+ if (schemaVersion !== 1 ||
34
+ typeof projectId !== "string" ||
35
+ typeof versionId !== "string" ||
36
+ typeof builtAt !== "number" ||
37
+ typeof builderVersion !== "string" ||
38
+ !isStringArray(capabilities) ||
39
+ !Array.isArray(workflows) ||
40
+ !Array.isArray(eventFilters) ||
41
+ !isRecord(bindings) ||
42
+ !isRecord(environments)) {
43
+ throw new Error("stored workflow manifest has an invalid shape");
44
+ }
45
+ return {
46
+ schemaVersion,
47
+ projectId,
48
+ versionId,
49
+ builtAt,
50
+ builderVersion,
51
+ capabilities,
52
+ workflows: workflows,
53
+ eventFilters: eventFilters,
54
+ bindings: bindings,
55
+ environments: environments,
56
+ };
57
+ }
58
+ function isRecord(value) {
59
+ return typeof value === "object" && value !== null && !Array.isArray(value);
60
+ }
61
+ function isStringArray(value) {
62
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
63
+ }
27
64
  // ---- Public factory ----
28
65
  /**
29
66
  * Build the Cloudflare-edge driver factory. The returned `DriverFactory`
@@ -137,14 +174,14 @@ export function createCloudflareEdgeDriver(opts) {
137
174
  return manifestStore.registerManifest({
138
175
  environment: args.environment,
139
176
  versionId: args.manifest.versionId,
140
- manifest: args.manifest,
177
+ manifest: serializeWorkflowManifest(args.manifest),
141
178
  });
142
179
  }
143
180
  async function getManifest(args) {
144
181
  const envelope = await manifestStore.getCurrent(args.environment);
145
182
  if (!envelope)
146
183
  return null;
147
- return envelope.manifest;
184
+ return deserializeWorkflowManifest(envelope.manifest);
148
185
  }
149
186
  async function trigger(workflow, input, triggerOpts) {
150
187
  assertNotShutdown();
@@ -192,7 +229,7 @@ export function createCloudflareEdgeDriver(opts) {
192
229
  message: `No manifest is registered for environment "${args.environment}".`,
193
230
  };
194
231
  }
195
- const manifest = stored.manifest;
232
+ const manifest = deserializeWorkflowManifest(stored.manifest);
196
233
  const eventId = args.envelope.metadata?.eventId ?? (await deriveStableEventId(args.envelope));
197
234
  const routed = routeEvent({
198
235
  manifest,
@@ -1 +1 @@
1
- {"version":3,"file":"event-handler.d.ts","sourceRoot":"","sources":["../src/event-handler.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAC7D,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAA;AAI7D,MAAM,WAAW,gBAAgB,CAAC,EAAE,GAAG,OAAO;IAC5C,sDAAsD;IACtD,aAAa,EAAE,eAAe,CAAA;IAC9B,6DAA6D;IAC7D,KAAK,EAAE,0BAA0B,CAAC,EAAE,CAAC,CAAA;IACrC,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,MAAM,CAAA;IAC1B,wBAAwB;IACxB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,sDAAsD;IACtD,UAAU,CAAC,EAAE;QACX,QAAQ,EAAE,MAAM,CAAA;QAChB,SAAS,EAAE,MAAM,CAAA;QACjB,cAAc,EAAE,MAAM,CAAA;QACtB,YAAY,CAAC,EAAE,MAAM,CAAA;KACtB,CAAA;IACD,uBAAuB;IACvB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CAChF;AAqBD,wBAAsB,iBAAiB,CAAC,EAAE,EACxC,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,gBAAgB,CAAC,EAAE,CAAC,GACzB,OAAO,CAAC,QAAQ,CAAC,CAqInB"}
1
+ {"version":3,"file":"event-handler.d.ts","sourceRoot":"","sources":["../src/event-handler.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAC7D,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAA;AAuD7D,MAAM,WAAW,gBAAgB,CAAC,EAAE,GAAG,OAAO;IAC5C,sDAAsD;IACtD,aAAa,EAAE,eAAe,CAAA;IAC9B,6DAA6D;IAC7D,KAAK,EAAE,0BAA0B,CAAC,EAAE,CAAC,CAAA;IACrC,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,MAAM,CAAA;IAC1B,wBAAwB;IACxB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,sDAAsD;IACtD,UAAU,CAAC,EAAE;QACX,QAAQ,EAAE,MAAM,CAAA;QAChB,SAAS,EAAE,MAAM,CAAA;QACjB,cAAc,EAAE,MAAM,CAAA;QACtB,YAAY,CAAC,EAAE,MAAM,CAAA;KACtB,CAAA;IACD,uBAAuB;IACvB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CAChF;AAqBD,wBAAsB,iBAAiB,CAAC,EAAE,EACxC,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,gBAAgB,CAAC,EAAE,CAAC,GACzB,OAAO,CAAC,QAAQ,CAAC,CAqInB"}
@@ -14,6 +14,39 @@
14
14
  import { deriveStableEventId } from "@voyantjs/workflows/events";
15
15
  import { routeEvent } from "@voyantjs/workflows-orchestrator";
16
16
  const ALLOWED_ENVS = new Set(["production", "preview", "development"]);
17
+ function deserializeWorkflowManifest(manifest) {
18
+ const { schemaVersion, projectId, versionId, builtAt, builderVersion, capabilities, workflows, eventFilters, bindings, environments, } = manifest;
19
+ if (schemaVersion !== 1 ||
20
+ typeof projectId !== "string" ||
21
+ typeof versionId !== "string" ||
22
+ typeof builtAt !== "number" ||
23
+ typeof builderVersion !== "string" ||
24
+ !isStringArray(capabilities) ||
25
+ !Array.isArray(workflows) ||
26
+ !Array.isArray(eventFilters) ||
27
+ !isRecord(bindings) ||
28
+ !isRecord(environments)) {
29
+ throw new Error("stored workflow manifest has an invalid shape");
30
+ }
31
+ return {
32
+ schemaVersion,
33
+ projectId,
34
+ versionId,
35
+ builtAt,
36
+ builderVersion,
37
+ capabilities,
38
+ workflows: workflows,
39
+ eventFilters: eventFilters,
40
+ bindings: bindings,
41
+ environments: environments,
42
+ };
43
+ }
44
+ function isRecord(value) {
45
+ return typeof value === "object" && value !== null && !Array.isArray(value);
46
+ }
47
+ function isStringArray(value) {
48
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
49
+ }
17
50
  const DEFAULT_TENANT_META = {
18
51
  tenantId: "default",
19
52
  projectId: "default",
@@ -44,7 +77,7 @@ export async function handleIngestEvent(req, deps) {
44
77
  message: `No manifest is registered for environment "${body.environment}".`,
45
78
  });
46
79
  }
47
- const manifest = manifestEnvelope.manifest;
80
+ const manifest = deserializeWorkflowManifest(manifestEnvelope.manifest);
48
81
  // Event id derivation — use the caller-stamped one when present, fall
49
82
  // back to a content-derived id so external callers without a forwarder
50
83
  // still get sensible idempotency.
@@ -1 +1 @@
1
- {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAE7D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAQrE;;;;GAIG;AACH,MAAM,WAAW,0BAA0B,CAAC,EAAE,GAAG,OAAO;IACtD,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,EAAE,CAAA;IAC5B,GAAG,CAAC,EAAE,EAAE,EAAE,GAAG;QAAE,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;KAAE,CAAA;CACxD;AAED,MAAM,WAAW,eAAe,CAAC,EAAE,GAAG,OAAO;IAC3C,KAAK,EAAE,0BAA0B,CAAC,EAAE,CAAC,CAAA;IACrC;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACtD,uBAAuB;IACvB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/E,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,MAAM,CAAA;IAC1B,0CAA0C;IAC1C,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,eAAe,CAAA;IAC/B;;;;;;OAMG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,oBAAoB,CAAA;IACzC;;;;;OAKG;IACH,UAAU,CAAC,EAAE;QACX,QAAQ,EAAE,MAAM,CAAA;QAChB,SAAS,EAAE,MAAM,CAAA;QACjB,cAAc,EAAE,MAAM,CAAA;QACtB,YAAY,CAAC,EAAE,MAAM,CAAA;KACtB,CAAA;CACF;AAED,wBAAsB,mBAAmB,CAAC,EAAE,EAC1C,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,eAAe,CAAC,EAAE,CAAC,GACxB,OAAO,CAAC,QAAQ,CAAC,CA4OnB"}
1
+ {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAE7D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAQrE;;;;GAIG;AACH,MAAM,WAAW,0BAA0B,CAAC,EAAE,GAAG,OAAO;IACtD,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,EAAE,CAAA;IAC5B,GAAG,CAAC,EAAE,EAAE,EAAE,GAAG;QAAE,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;KAAE,CAAA;CACxD;AAED,MAAM,WAAW,eAAe,CAAC,EAAE,GAAG,OAAO;IAC3C,KAAK,EAAE,0BAA0B,CAAC,EAAE,CAAC,CAAA;IACrC;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACtD,uBAAuB;IACvB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/E,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,MAAM,CAAA;IAC1B,0CAA0C;IAC1C,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,eAAe,CAAA;IAC/B;;;;;;OAMG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,oBAAoB,CAAA;IACzC;;;;;OAKG;IACH,UAAU,CAAC,EAAE;QACX,QAAQ,EAAE,MAAM,CAAA;QAChB,SAAS,EAAE,MAAM,CAAA;QACjB,cAAc,EAAE,MAAM,CAAA;QACtB,YAAY,CAAC,EAAE,MAAM,CAAA;KACtB,CAAA;CACF;AAED,wBAAsB,mBAAmB,CAAC,EAAE,EAC1C,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,eAAe,CAAC,EAAE,CAAC,GACxB,OAAO,CAAC,QAAQ,CAAC,CA4OnB"}
package/dist/worker.js CHANGED
@@ -1,3 +1,4 @@
1
+ // agent-quality: file-size exception -- owner: workflows-orchestrator-cloudflare; existing module stays co-located until a dedicated split preserves behavior and tests.
1
2
  // Public HTTP surface of the Cloudflare orchestrator. The outer
2
3
  // Worker receives a request, resolves the run DO by id (or creates
3
4
  // one for a new trigger), and forwards to the DO. This layer owns
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voyantjs/workflows-orchestrator-cloudflare",
3
- "version": "0.107.4",
3
+ "version": "0.107.6",
4
4
  "description": "Cloudflare Worker + Durable Object adapter for @voyantjs/workflows-orchestrator. Dispatches workflow-step requests to tenant Workers via a Workers-for-Platforms dispatch namespace.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -26,8 +26,8 @@
26
26
  "NOTICE"
27
27
  ],
28
28
  "dependencies": {
29
- "@voyantjs/workflows-orchestrator": "^0.107.4",
30
- "@voyantjs/workflows": "^0.107.4"
29
+ "@voyantjs/workflows-orchestrator": "^0.107.6",
30
+ "@voyantjs/workflows": "^0.107.6"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@cloudflare/vitest-pool-workers": "^0.15.1",
@@ -0,0 +1,107 @@
1
+ import { __resetRegistry } from "@voyantjs/workflows"
2
+ import { handleStepRequest } from "@voyantjs/workflows/handler"
3
+ import { beforeEach } from "vitest"
4
+ import {
5
+ createServiceBindingDispatcher,
6
+ type DurableObjectNamespaceLike,
7
+ type DurableObjectStorageLike,
8
+ handleDurableObjectRequest,
9
+ type ServiceBindingLike,
10
+ } from "../index.js"
11
+
12
+ export interface AlarmTrackingStorage extends DurableObjectStorageLike {
13
+ _alarm: number | null
14
+ _alarmCalls: number
15
+ _deleteAlarmCalls: number
16
+ }
17
+
18
+ export function makeStorage(): AlarmTrackingStorage {
19
+ const map = new Map<string, unknown>()
20
+ const s: AlarmTrackingStorage = {
21
+ _alarm: null,
22
+ _alarmCalls: 0,
23
+ _deleteAlarmCalls: 0,
24
+ async get<T>(key: string): Promise<T | undefined> {
25
+ return map.get(key) as T | undefined
26
+ },
27
+ async put<T>(key: string, value: T): Promise<void> {
28
+ map.set(key, value)
29
+ },
30
+ async delete(key) {
31
+ return map.delete(key)
32
+ },
33
+ async list<T>(options = {}) {
34
+ const out = new Map<string, T>()
35
+ for (const [k, v] of map) {
36
+ if (options.prefix && !k.startsWith(options.prefix)) continue
37
+ out.set(k, v as T)
38
+ if (options.limit && out.size >= options.limit) break
39
+ }
40
+ return out
41
+ },
42
+ async getAlarm() {
43
+ return s._alarm
44
+ },
45
+ async setAlarm(wakeAt) {
46
+ s._alarm = wakeAt
47
+ s._alarmCalls += 1
48
+ },
49
+ async deleteAlarm() {
50
+ s._alarm = null
51
+ s._deleteAlarmCalls += 1
52
+ },
53
+ }
54
+ return s
55
+ }
56
+
57
+ export function inProcessBinding(): ServiceBindingLike {
58
+ return {
59
+ async fetch(req: Request): Promise<Response> {
60
+ const body = await req.json()
61
+ const out = await handleStepRequest(body)
62
+ return new Response(JSON.stringify(out.body), {
63
+ status: out.status,
64
+ headers: { "content-type": "application/json" },
65
+ })
66
+ },
67
+ }
68
+ }
69
+
70
+ export function inProcessRunDONamespace(): DurableObjectNamespaceLike<string> & {
71
+ _storages: Map<string, DurableObjectStorageLike>
72
+ } {
73
+ const storages = new Map<string, DurableObjectStorageLike>()
74
+ const binding = inProcessBinding()
75
+ return {
76
+ _storages: storages,
77
+ idFromName(name) {
78
+ return name
79
+ },
80
+ get(id: string) {
81
+ let storage = storages.get(id)
82
+ if (!storage) {
83
+ storage = makeStorage()
84
+ storages.set(id, storage)
85
+ }
86
+ return {
87
+ async fetch(req: Request): Promise<Response> {
88
+ return handleDurableObjectRequest(req, {
89
+ storage: storage!,
90
+ dispatcher: createServiceBindingDispatcher({ binding }),
91
+ })
92
+ },
93
+ }
94
+ },
95
+ }
96
+ }
97
+
98
+ export const tenantMeta = {
99
+ tenantId: "tnt_t",
100
+ projectId: "prj_t",
101
+ organizationId: "org_t",
102
+ tenantScript: "tenant-worker-a",
103
+ }
104
+
105
+ beforeEach(() => {
106
+ __resetRegistry()
107
+ })
@@ -26,6 +26,8 @@
26
26
 
27
27
  import type { StepJournalEntry, StepRunner } from "@voyantjs/workflows/handler"
28
28
 
29
+ const STEP_RESPONSE_AUTH_HEADER = "x-voyant-step-response-auth"
30
+
29
31
  /**
30
32
  * Minimal subset of `DurableObjectNamespace` that the runner actually
31
33
  * uses. Matches the shape exposed by `@cloudflare/containers`'
@@ -256,6 +258,18 @@ export function createCfContainerStepRunner(deps: CfContainerRunnerDeps): StepRu
256
258
  new Error(`container returned HTTP ${response.status}: ${text}`),
257
259
  )
258
260
  }
261
+ if (deps.sign) {
262
+ const signature = response.headers.get(STEP_RESPONSE_AUTH_HEADER)
263
+ const expected = await deps.sign(text)
264
+ if (!signature || !constantTimeEquals(signature, expected)) {
265
+ return failed(
266
+ attempt,
267
+ startedAt,
268
+ "CONTAINER_RESPONSE_SIGNATURE_INVALID",
269
+ new Error("container response signature is missing or invalid"),
270
+ )
271
+ }
272
+ }
259
273
  try {
260
274
  const entry = JSON.parse(text) as StepJournalEntry
261
275
  // Trust the container's own timestamps; they reflect the actual
@@ -272,6 +286,15 @@ export function createCfContainerStepRunner(deps: CfContainerRunnerDeps): StepRu
272
286
  }
273
287
  }
274
288
 
289
+ function constantTimeEquals(a: string, b: string): boolean {
290
+ const length = Math.max(a.length, b.length, 1)
291
+ let diff = a.length === b.length ? 0 : 1
292
+ for (let i = 0; i < length; i++) {
293
+ diff |= (a.charCodeAt(i) | 0) ^ (b.charCodeAt(i) | 0)
294
+ }
295
+ return diff === 0
296
+ }
297
+
275
298
  function failed(attempt: number, startedAt: number, code: string, err: unknown): StepJournalEntry {
276
299
  const e = err instanceof Error ? err : new Error(String(err))
277
300
  return {
@@ -1,4 +1,5 @@
1
1
  // Mode 1 driver — Cloudflare edge composition.
2
+ // agent-quality: file-size exception -- Public edge driver factory currently owns manifest, trigger, event-ingest, concurrency, and admin wiring; split only with a dedicated driver-surface refactor.
2
3
  //
3
4
  // `createApp({ workflows: { driver: createCloudflareEdgeDriver({ ... }) } })`
4
5
  // is the entry point for any deployment that runs the orchestrator on
@@ -91,6 +92,61 @@ const DEFAULT_TENANT_META = {
91
92
  organizationId: "default",
92
93
  }
93
94
 
95
+ function serializeWorkflowManifest(manifest: WorkflowManifest): Record<string, unknown> {
96
+ return { ...manifest }
97
+ }
98
+
99
+ function deserializeWorkflowManifest(manifest: Record<string, unknown>): WorkflowManifest {
100
+ const {
101
+ schemaVersion,
102
+ projectId,
103
+ versionId,
104
+ builtAt,
105
+ builderVersion,
106
+ capabilities,
107
+ workflows,
108
+ eventFilters,
109
+ bindings,
110
+ environments,
111
+ } = manifest
112
+
113
+ if (
114
+ schemaVersion !== 1 ||
115
+ typeof projectId !== "string" ||
116
+ typeof versionId !== "string" ||
117
+ typeof builtAt !== "number" ||
118
+ typeof builderVersion !== "string" ||
119
+ !isStringArray(capabilities) ||
120
+ !Array.isArray(workflows) ||
121
+ !Array.isArray(eventFilters) ||
122
+ !isRecord(bindings) ||
123
+ !isRecord(environments)
124
+ ) {
125
+ throw new Error("stored workflow manifest has an invalid shape")
126
+ }
127
+
128
+ return {
129
+ schemaVersion,
130
+ projectId,
131
+ versionId,
132
+ builtAt,
133
+ builderVersion,
134
+ capabilities,
135
+ workflows: workflows as WorkflowManifest["workflows"],
136
+ eventFilters: eventFilters as WorkflowManifest["eventFilters"],
137
+ bindings: bindings as WorkflowManifest["bindings"],
138
+ environments: environments as WorkflowManifest["environments"],
139
+ }
140
+ }
141
+
142
+ function isRecord(value: unknown): value is Record<string, unknown> {
143
+ return typeof value === "object" && value !== null && !Array.isArray(value)
144
+ }
145
+
146
+ function isStringArray(value: unknown): value is string[] {
147
+ return Array.isArray(value) && value.every((item) => typeof item === "string")
148
+ }
149
+
94
150
  // ---- Public factory ----
95
151
 
96
152
  /**
@@ -251,7 +307,7 @@ export function createCloudflareEdgeDriver(opts: CloudflareEdgeDriverOptions): D
251
307
  return manifestStore.registerManifest({
252
308
  environment: args.environment,
253
309
  versionId: args.manifest.versionId,
254
- manifest: args.manifest as unknown as Record<string, unknown>,
310
+ manifest: serializeWorkflowManifest(args.manifest),
255
311
  })
256
312
  }
257
313
 
@@ -260,7 +316,7 @@ export function createCloudflareEdgeDriver(opts: CloudflareEdgeDriverOptions): D
260
316
  }): Promise<WorkflowManifest | null> {
261
317
  const envelope = await manifestStore.getCurrent(args.environment)
262
318
  if (!envelope) return null
263
- return envelope.manifest as unknown as WorkflowManifest
319
+ return deserializeWorkflowManifest(envelope.manifest)
264
320
  }
265
321
 
266
322
  async function trigger<TIn, TOut>(
@@ -327,7 +383,7 @@ export function createCloudflareEdgeDriver(opts: CloudflareEdgeDriverOptions): D
327
383
  message: `No manifest is registered for environment "${args.environment}".`,
328
384
  }
329
385
  }
330
- const manifest = stored.manifest as unknown as WorkflowManifest
386
+ const manifest = deserializeWorkflowManifest(stored.manifest)
331
387
  const eventId = args.envelope.metadata?.eventId ?? (await deriveStableEventId(args.envelope))
332
388
  const routed = routeEvent({
333
389
  manifest,
@@ -21,6 +21,57 @@ import type { DurableObjectNamespaceLike } from "./worker.js"
21
21
 
22
22
  const ALLOWED_ENVS = new Set(["production", "preview", "development"])
23
23
 
24
+ function deserializeWorkflowManifest(manifest: Record<string, unknown>): WorkflowManifest {
25
+ const {
26
+ schemaVersion,
27
+ projectId,
28
+ versionId,
29
+ builtAt,
30
+ builderVersion,
31
+ capabilities,
32
+ workflows,
33
+ eventFilters,
34
+ bindings,
35
+ environments,
36
+ } = manifest
37
+
38
+ if (
39
+ schemaVersion !== 1 ||
40
+ typeof projectId !== "string" ||
41
+ typeof versionId !== "string" ||
42
+ typeof builtAt !== "number" ||
43
+ typeof builderVersion !== "string" ||
44
+ !isStringArray(capabilities) ||
45
+ !Array.isArray(workflows) ||
46
+ !Array.isArray(eventFilters) ||
47
+ !isRecord(bindings) ||
48
+ !isRecord(environments)
49
+ ) {
50
+ throw new Error("stored workflow manifest has an invalid shape")
51
+ }
52
+
53
+ return {
54
+ schemaVersion,
55
+ projectId,
56
+ versionId,
57
+ builtAt,
58
+ builderVersion,
59
+ capabilities,
60
+ workflows: workflows as WorkflowManifest["workflows"],
61
+ eventFilters: eventFilters as WorkflowManifest["eventFilters"],
62
+ bindings: bindings as WorkflowManifest["bindings"],
63
+ environments: environments as WorkflowManifest["environments"],
64
+ }
65
+ }
66
+
67
+ function isRecord(value: unknown): value is Record<string, unknown> {
68
+ return typeof value === "object" && value !== null && !Array.isArray(value)
69
+ }
70
+
71
+ function isStringArray(value: unknown): value is string[] {
72
+ return Array.isArray(value) && value.every((item) => typeof item === "string")
73
+ }
74
+
24
75
  export interface EventHandlerDeps<Id = unknown> {
25
76
  /** KV-backed manifest store (read-only path here). */
26
77
  manifestStore: CfManifestStore
@@ -87,7 +138,7 @@ export async function handleIngestEvent<Id>(
87
138
  message: `No manifest is registered for environment "${body.environment}".`,
88
139
  })
89
140
  }
90
- const manifest = manifestEnvelope.manifest as unknown as WorkflowManifest
141
+ const manifest = deserializeWorkflowManifest(manifestEnvelope.manifest)
91
142
 
92
143
  // Event id derivation — use the caller-stamped one when present, fall
93
144
  // back to a content-derived id so external callers without a forwarder
package/src/worker.ts CHANGED
@@ -1,3 +1,4 @@
1
+ // agent-quality: file-size exception -- owner: workflows-orchestrator-cloudflare; existing module stays co-located until a dedicated split preserves behavior and tests.
1
2
  // Public HTTP surface of the Cloudflare orchestrator. The outer
2
3
  // Worker receives a request, resolves the run DO by id (or creates
3
4
  // one for a new trigger), and forwards to the DO. This layer owns