Skip to content

Route multi-step routines onto Interchange's native workflow trigger - #256

Merged
TheGreatAxios merged 3 commits into
mainfrom
cl-native-multistep
Aug 21, 2026
Merged

TheGreatAxios merged 3 commits into
mainfrom
cl-native-multistep

Conversation

@TheGreatAxios

Copy link
Copy Markdown
Contributor

Summary

  • Routines could only launch through @corbits/folded-runs, whose readFoldedBody has always required exactly one step (MultiStepFoldUnsupportedError) — a multi-step definition threw synchronously on the very first launch attempt.
  • A multi-step definition can only exist here as a code-sourced @intx/workflow package deployed via POST /workflows/deployments, so a live, self-anchored deployment already exists for it by construction. The routine launcher now catches MultiStepFoldUnsupportedError and fires that deployment directly with a signed mail message through SidecarRouter.routeMail (a public method already in every FoldedRunsDeps caller's hands) — the same native primitive POST /workflows/:id/mail uses, assembled from the same public @intx/mime/@intx/crypto primitives since that route's own trigger function isn't exported from @intx/hub-api.
  • Wires @intx/hub-api's exported createMailTriggeredRunGrantsMaterializer into the hub's sidecar lookups (apps/hub/src/index.ts). This wasn't wired before — only the dedicated HTTP trigger route staged a run's grants inline — so a plain mail delivered to a workflow deployment (as this new path does) would otherwise reach the sidecar without its run's grants ever being materialized. This closes a real gap for any native mail-triggered run, not only routines.
  • Rewrote packages/hub-client/test/routine-launchability.test.ts, which asserted every DEFAULT_WORKFLOWS entry must be single-step — the exact restriction this PR removes. It now asserts structural well-formedness (stepOrder/steps agree) instead.

Design notes

  • Single-step, hand-authored definitions are unchanged — they carry no source of their own and still need launchFoldedRun's render-and-deploy bridge (itself a call into Interchange's real deployAdoptedWorkflowFromSource, not a reimplementation). This is a deliberate split by definition shape (does the definition already have real deployed source, or does one need to be rendered for it), not two launchers competing for the same case.
  • Completion/status observability: GET /routines/:id/runs's resolveRunSummary already reads status generically off workflow_run by id, so a native run's status is observable with zero further wiring. The caveat: the returned run id is the deployment's own anchor — the same coarse, deployment-level handle POST /workflows/:id/mail itself returns synchronously (its true per-fire child run id is minted by the supervisor and isn't known until its own RunStarted event lands). A routine that fires the same shared deployment repeatedly will show that one anchor id/status rather than a distinct row per fire; resolving the precise per-fire child run (poll GET /workflows/:id/runs for a new id, proven pattern in scripts/e2e/cl-6324-launch-proof.ts) is a follow-up, not done here.
  • Child workflows: already fully admitted by the DAG supervisor and proven end-to-end by the existing scripts/e2e/cl-6324-launch-proof.ts. Nothing in this change touches that mechanism — the job here was only making an already-working native execution reachable from a routine at all.
  • Vendor untouched: only consumes already-exported functions (createMailTriggeredRunGrantsMaterializer from @intx/hub-api, SidecarRouter.routeMail/sendRunGrants from @intx/hub-sessions, plus public @intx/mime/@intx/crypto/@intx/types primitives). Nothing under vendor/intx/ was modified.
  • Left workflows/last-30-days-research folded to one step (CL-6495) — restoring its original six-step content is a content change, out of scope for this launch-path fix, now that multi-step CAN launch.

Test plan

  • apps/hub full suite: 153 pass / 0 fail (includes 12 pre-existing routine-launcher tests, unchanged — proves single-step regression protection)
  • New: native-workflow-routine-launch.test.ts — fires a signed mail at a live deployment and returns its anchor id; fails loud (named error) when no live deployment exists or the only one is terminal; fails loud (not silently) when the deployment is unrouteable
  • New: routine-launcher multi-step routing tests — multi-step definitions never touch the folded launcher, route through the native trigger instead, still fire with a placeholder when the routine stored no input, and join the delivery workbench using the native deployment's own address
  • packages/hub-client full suite: 162 pass / 0 fail
  • bunx tsc --noEmit clean on apps/hub and packages/hub-client
  • Not proven here: a live sidecar + DAG supervisor actually executing StepStarted/StepCompleted across multiple steps for a routine-launched run — that requires a running sidecar, out of reach on this timeboxed pass; the underlying DAG mechanism is vendor-owned, unchanged, and already proven by scripts/e2e/cl-6324-launch-proof.ts

Covers triggerNativeWorkflowRoutineRun (fires a signed mail at a
definition's live deployment, fails loud when none exists or the
deployment is unrouteable) and the routine launcher's routing: a
multi-step definition goes through the native trigger instead of the
folded launcher, while single-step routines are unaffected. Also
rewrites routine-launchability.test.ts, which previously asserted every
default workflow must be single-step — the exact restriction this
change removes.
…ow trigger

Routines could only launch through @corbits/folded-runs, whose
readFoldedBody has always required exactly one step -- a multi-step
definition threw MultiStepFoldUnsupportedError synchronously, which
surfaced as a bare 500 on the first launch attempt.

A multi-step definition can only exist in this repo as a code-sourced
@intx/workflow package deployed through POST /workflows/deployments,
so a live, self-anchored deployment already exists for it. The routine
launcher now catches MultiStepFoldUnsupportedError and fires that
deployment directly with a signed mail message via SidecarRouter's
public routeMail -- the same native primitive the dedicated
POST /workflows/:id/mail route uses, assembled from the same public
@intx/mime/@intx/crypto primitives since that route's own trigger
function isn't exported. Single-step, hand-authored definitions are
unchanged: they have no source of their own and still need
launchFoldedRun's render-and-deploy bridge, so this is a deliberate
split by definition shape, not two competing launchers for one case.

Also wires @intx/hub-api's createMailTriggeredRunGrantsMaterializer
into the hub's sidecar lookups. Without it, a plain mail delivered to
a workflow deployment's address (as this new path does) would reach
the sidecar without its run's grants ever being materialized, leaving
it authorized for nothing. This was previously unwired -- only the
dedicated HTTP trigger route staged a run's grants inline -- so this
closes a real gap for any native mail-triggered run, not just routines.

GET /routines/:id/runs already reads status generically off
workflow_run by id, so a native run's status/completion is observable
there with no further wiring: the returned run id is the deployment's
own anchor, the same coarse per-deployment handle
POST /workflows/:id/mail itself returns synchronously.
@TheGreatAxios
TheGreatAxios merged commit d345325 into main Aug 21, 2026
5 checks passed
@TheGreatAxios
TheGreatAxios deleted the cl-native-multistep branch August 25, 2026 15:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant