Skip to main content

Workflow service (apps/workflow)

Status: scaffold. Nothing invokes it.

This service is the M4 approval/compliance substrate: durable approval chains, SLA timers with escalation, and an audit trail. It exists so the whole fleet can run on Bun with no mixed-runtime seam — which is why it is not Temporal.

Why not Temporal​

Temporal is Apache 2.0 and genuinely open source, and it has years of production hardening for exactly this problem class. It was rejected on one concrete technical fact: Temporal's TypeScript SDK worker does not run on Bun (its native bindings, and Deno has the identical problem for the identical reason). Adopting it would have forced a Node.js exception into a fleet whose entire premise is running on one runtime.

Building a general-purpose durable execution engine from scratch was also considered and deliberately rejected. That category — deterministic replay of arbitrary code, event-sourced history with strict ordering, non-determinism detection, workflow versioning — is a multi-year distributed-systems problem. The actual requirement here is narrower: durable state machines plus durable job scheduling, not arbitrary-code durability.

The composed layer​

Three proven, open-source, Bun-verified primitives, each doing the part it is genuinely good at:

PrimitiveLicenseRole
DBOS Transact (TS)MITDurability substrate. A library, not a separate orchestrator server — it checkpoints each step's state transactionally into the same tenant-isolated Postgres as everything else. A crash mid-chain resumes from the last checkpoint, with no extra process to deploy and no native-binding risk.
pg-bossMIT-family (CI-verified Bun support)Durable scheduling — SLA timers, escalation reminders, scheduled policy evaluation. Retryable, cron-capable, dead-letter-backed, SKIP LOCKED-based.
XStateMIT, zero native dependenciesThe declarative shape of an approval chain: stages, transitions, roles, delegation. This is what M4's no-code UI is meant to compile to.

Because DBOS checkpoints into the same Postgres used everywhere else, the durability story is one story: same transaction boundary, same RLS, same tenant_id model.

What actually exists​

FileContents
src/index.tsDBOS.setConfig + DBOS.launch(), createScheduler(), graceful SIGTERM shutdown calling boss.stop() and DBOS.shutdown()
src/dbos-workflows.tsGuestLogisticsWorkflow — a @DBOS.workflow() static method that starts an XState actor and calls a @DBOS.step() that currently only console.logs the stage
src/machines/guest-logistics.machine.tsThe XState machine definition for M3 Guest Logistics
src/scheduler.tspg-boss scheduler construction

DBOS.setConfig is pointed at DATABASE_URL — the owning/admin role, not RUNTIME_DATABASE_URL. That is a deliberate consequence of DBOS managing its own state tables (DDL, not just DML), but it is the one place in the fleet where a service connects as the owner, and it means DBOS's own storage is outside the RLS boundary. Worth a conscious decision before this service handles real approval data.

How to finish it​

  1. Verify the DBOS API surface. dbos-workflows.ts says so itself: the decorator/registration API moves fast and the current code is illustrative. Confirm @DBOS.workflow() / @DBOS.step() against the DBOS Transact TS docs current at implementation time.
  2. Make the step do real work. recordStage should persist the stage and the evidence trail to Postgres through @campus/db — and it must go through the mutation-service / withTenant discipline, not a direct insert, or the workflow's writes will be invisible to the audit log and unisolated from other tenants.
  3. Add the real trigger. index.ts has a commented-out example invocation and a void GuestLogisticsWorkflow; to keep the import live. The intended trigger is a Hono API call — a route that starts the workflow for a guest visit and returns the workflow handle.
  4. Add the SLA timer. One pg-boss scheduled job per stage deadline, with escalation on expiry. This is the first thing to prove, because it is the part a visual tool cannot do.
  5. Crash-test it. The verification the architecture doc specifies: simulate a crash and restart mid-chain and confirm the workflow resumes from its last checkpoint. Then load-test a multi-month-duration synthetic scenario (a simulated admissions cycle) before relying on it for a real one.

The architecture doc is explicit that this composed layer is less battle-tested than Temporal for multi-month workflows, and that keeping its responsibilities narrow (approval chains, SLA timers, audit trail — not arbitrary business logic) is the mitigation. It is also flagged as an intended open-source release the team maintains, which means it needs documentation and a real test suite of its own — currently it has neither, and bun test --pass-with-no-tests is how it stays green.

The one workflow-shaped thing that deliberately is not here​

attendance_delegations (ask another faculty member to take a class) is a plain table with a simple accept/decline API, not routed through this service. A same-day delegation is not a multi-month durable process, and coupling M0 to a service that is not proven yet would be the wrong dependency direction. It graduates here only if delegation grows into multi-level approval chains.