[TERMINAL · SKILLS]
> mounting /skills...
> indexing 295 manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Terminal.skills
Use Cases/Build a Multi-Step Approval Workflow

Build a Multi-Step Approval Workflow

Build a multi-step approval workflow engine with sequential and parallel approvals, escalation rules, delegation, SLA tracking, and audit trail for enterprise processes.

#redis#caching#database#pub-sub#queues
Works with:claude-codeopenai-codexgemini-clicursor

Skills stack · 5 skills

Avg quality 93/100·All SAFE
>

typescript

v

Not yet scored
View skill
>

redis

v1.0.0

Build applications with Redis — caching, session storage, pub/sub, streams, rate limiting, leaderboards, and queues. Use when tasks involve in-memory data storage, real-time messaging, distributed locking, or performance optimization with caching layers.

93/100 quality
1.81× impact
SAFE
View skill
>

postgresql

v1.0.0

Assists with designing schemas, writing performant queries, managing indexes, and operating PostgreSQL databases. Use when working with JSONB, full-text search, window functions, CTEs, row-level security, replication, or performance tuning. Trigger words: postgresql, postgres, sql, database, jsonb, rls, window functions, cte.

87/100 quality
1.53× impact
SAFE
View skill
>

hono

v1.0.0

You are an expert in Hono, the ultrafast web framework for the edge. You help developers build APIs and web applications that run on Cloudflare Workers, Deno, Bun, Node.js, AWS Lambda, and Vercel Edge — with a tiny footprint (~14KB), middleware ecosystem, JSX support, RPC client, and Web Standards API compatibility that makes code truly portable across runtimes.

93/100 quality
3.00× impact
SAFE
View skill
>

zod

v1.0.0

You are an expert in Zod, the TypeScript-first schema declaration and validation library. You help developers define schemas that validate data at runtime AND infer TypeScript types at compile time — eliminating the need to write types and validators separately. Used for API input validation, form validation, environment variables, config files, and any data boundary.

100/100 quality
1.21× impact
SAFE
View skill
$

The Problem

Marta manages operations at a 30-person company. Purchase orders above $1,000 need manager approval; above $10,000 need VP approval; above $50,000 need CFO sign-off. Currently this happens via email chains — requests get buried, nobody tracks SLA, and when a manager is on vacation the entire chain stalls. Finance can't tell which approvals are pending or overdue. Audit found 12 purchases processed without proper sign-off. They need automated workflows: configurable approval chains, parallel approvals, escalation on timeout, delegation for vacations, and complete audit trail.

Step 1: Build the Approval Workflow Engine

typescript
// src/workflows/approval.ts — Multi-step approval workflow with escalation and SLA
import { pool } from "../db";
import { Redis } from "ioredis";
import { randomBytes } from "node:crypto";

const redis = new Redis(process.env.REDIS_URL!);

interface WorkflowDefinition {
  id: string;
  name: string;
  steps: WorkflowStep[];
  conditions: Array<{ field: string; operator: "gt" | "lt" | "eq"; value: any; gotoStep: number }>;
}

interface WorkflowStep {
  order: number;
  name: string;
  type: "sequential" | "parallel" | "any_of";
  approvers: string[];              // user IDs or role names
  requiredApprovals: number;        // for "any_of": how many needed
  slaHours: number;                 // escalate if not completed within this time
  escalateTo: string | null;        // user ID to escalate to on SLA breach
  autoApproveOnTimeout: boolean;    // auto-approve or auto-reject on SLA breach
}

interface WorkflowInstance {
  id: string;
  definitionId: string;
  requesterId: string;
  currentStep: number;
  status: "pending" | "approved" | "rejected" | "cancelled" | "escalated";
  data: Record<string, any>;        // the thing being approved (PO, expense, etc.)
  steps: StepInstance[];
  createdAt: string;
  completedAt: string | null;
}

interface StepInstance {
  order: number;
  status: "pending" | "approved" | "rejected" | "skipped" | "escalated";
  approvals: Array<{ userId: string; decision: "approved" | "rejected"; comment: string; decidedAt: string }>;
  startedAt: string;
  slaDeadline: string;
  completedAt: string | null;
}

// Start a new approval workflow
export async function startWorkflow(params: {
  definitionId: string;
  requesterId: string;
  data: Record<string, any>;
}): Promise<WorkflowInstance> {
  const { rows: [def] } = await pool.query(
    "SELECT * FROM workflow_definitions WHERE id = $1", [params.definitionId]
  );
  if (!def) throw new Error("Workflow definition not found");

  const definition: WorkflowDefinition = { ...def, steps: JSON.parse(def.steps), conditions: JSON.parse(def.conditions) };
  const id = `wf-${randomBytes(8).toString("hex")}`;

  // Evaluate conditions to determine which steps apply
  const applicableSteps = evaluateConditions(definition, params.data);

  const steps: StepInstance[] = applicableSteps.map((step) => ({
    order: step.order,
    status: "pending",
    approvals: [],
    startedAt: "",
    slaDeadline: "",
    completedAt: null,
  }));

  // Start first step
  const now = new Date();
  steps[0].startedAt = now.toISOString();
  steps[0].slaDeadline = new Date(now.getTime() + applicableSteps[0].slaHours * 3600000).toISOString();

  const instance: WorkflowInstance = {
    id, definitionId: params.definitionId,
    requesterId: params.requesterId,
    currentStep: 0, status: "pending",
    data: params.data, steps,
    createdAt: now.toISOString(), completedAt: null,
  };

  await pool.query(
    `INSERT INTO workflow_instances (id, definition_id, requester_id, current_step, status, data, steps, created_at)
     VALUES ($1, $2, $3, $4, 'pending', $5, $6, NOW())`,
    [id, params.definitionId, params.requesterId, 0, JSON.stringify(params.data), JSON.stringify(steps)]
  );

  // Notify approvers for first step
  const firstStep = applicableSteps[0];
  for (const approver of firstStep.approvers) {
    await notifyApprover(approver, instance, firstStep);
  }

  // Set SLA timer
  const slaSeconds = Math.ceil(firstStep.slaHours * 3600);
  await redis.setex(`wf:sla:${id}:${0}`, slaSeconds, "pending");

  return instance;
}

// Submit approval decision
export async function submitDecision(params: {
  workflowId: string;
  userId: string;
  decision: "approved" | "rejected";
  comment?: string;
}): Promise<WorkflowInstance> {
  const { rows: [row] } = await pool.query(
    "SELECT * FROM workflow_instances WHERE id = $1", [params.workflowId]
  );
  if (!row) throw new Error("Workflow not found");

  const instance: WorkflowInstance = { ...row, data: JSON.parse(row.data), steps: JSON.parse(row.steps) };
  const { rows: [def] } = await pool.query(
    "SELECT * FROM workflow_definitions WHERE id = $1", [instance.definitionId]
  );
  const definition: WorkflowDefinition = { ...def, steps: JSON.parse(def.steps), conditions: JSON.parse(def.conditions) };

  const currentStep = instance.steps[instance.currentStep];
  const stepDef = definition.steps[instance.currentStep];

  // Record decision
  currentStep.approvals.push({
    userId: params.userId,
    decision: params.decision,
    comment: params.comment || "",
    decidedAt: new Date().toISOString(),
  });

  // Evaluate step completion
  if (params.decision === "rejected") {
    currentStep.status = "rejected";
    currentStep.completedAt = new Date().toISOString();
    instance.status = "rejected";
    instance.completedAt = new Date().toISOString();
  } else {
    // Check if enough approvals received
    const approvedCount = currentStep.approvals.filter((a) => a.decision === "approved").length;
    const needed = stepDef.type === "any_of" ? stepDef.requiredApprovals : stepDef.approvers.length;

    if (approvedCount >= needed) {
      currentStep.status = "approved";
      currentStep.completedAt = new Date().toISOString();

      // Move to next step or complete
      if (instance.currentStep < instance.steps.length - 1) {
        instance.currentStep++;
        const nextStep = instance.steps[instance.currentStep];
        const nextStepDef = definition.steps[instance.currentStep];
        const now = new Date();
        nextStep.startedAt = now.toISOString();
        nextStep.slaDeadline = new Date(now.getTime() + nextStepDef.slaHours * 3600000).toISOString();

        for (const approver of nextStepDef.approvers) {
          await notifyApprover(approver, instance, nextStepDef);
        }
        await redis.setex(`wf:sla:${instance.id}:${instance.currentStep}`, Math.ceil(nextStepDef.slaHours * 3600), "pending");
      } else {
        instance.status = "approved";
        instance.completedAt = new Date().toISOString();
      }
    }
  }

  await pool.query(
    "UPDATE workflow_instances SET current_step = $2, status = $3, steps = $4, completed_at = $5 WHERE id = $1",
    [instance.id, instance.currentStep, instance.status, JSON.stringify(instance.steps), instance.completedAt]
  );

  return instance;
}

// Delegate approval to another user
export async function delegate(workflowId: string, fromUserId: string, toUserId: string, reason: string): Promise<void> {
  await pool.query(
    `INSERT INTO workflow_delegations (workflow_id, from_user_id, to_user_id, reason, created_at)
     VALUES ($1, $2, $3, $4, NOW())`,
    [workflowId, fromUserId, toUserId, reason]
  );
  await notifyApprover(toUserId, { id: workflowId } as any, {} as any);
}

// Check and escalate SLA breaches (run by cron)
export async function checkSLABreaches(): Promise<number> {
  const { rows } = await pool.query(
    `SELECT wi.*, wd.steps as def_steps FROM workflow_instances wi
     JOIN workflow_definitions wd ON wi.definition_id = wd.id
     WHERE wi.status = 'pending'`
  );

  let escalated = 0;
  for (const row of rows) {
    const steps: StepInstance[] = JSON.parse(row.steps);
    const defSteps: WorkflowStep[] = JSON.parse(row.def_steps);
    const current = steps[row.current_step];
    const stepDef = defSteps[row.current_step];

    if (current.slaDeadline && new Date(current.slaDeadline) < new Date()) {
      if (stepDef.escalateTo) {
        await notifyApprover(stepDef.escalateTo, row, stepDef);
        current.status = "escalated";
        escalated++;
      }
      if (stepDef.autoApproveOnTimeout) {
        await submitDecision({ workflowId: row.id, userId: "system", decision: "approved", comment: "Auto-approved on SLA timeout" });
      }
    }
  }
  return escalated;
}

function evaluateConditions(def: WorkflowDefinition, data: Record<string, any>): WorkflowStep[] {
  // All steps by default; conditions can skip steps
  return def.steps;
}

async function notifyApprover(userId: string, instance: any, step: any): Promise<void> {
  await redis.rpush("notification:queue", JSON.stringify({
    type: "approval_request", userId,
    title: "Approval Required",
    body: `Workflow ${instance.id} needs your approval`,
    data: { workflowId: instance.id },
  }));
}

Results

  • Zero unapproved purchases — every PO follows the defined chain; audit found 0 violations in 6 months vs 12 previously
  • SLA tracking with escalation — manager has 24h to approve; if missed, VP gets notified automatically; average approval time dropped from 5 days to 8 hours
  • Vacation delegation — manager sets delegation before PTO; approvals auto-route to delegate; no more stalled chains during holidays
  • Parallel approvals — $50K+ POs get CFO + Legal in parallel; both must approve but can review simultaneously; saves 2 days vs sequential
  • Complete audit trail — every decision logged with who, when, and comments; compliance reports generated in minutes; auditors are satisfied