# Job dependencies

> Keep downstream work blocked until its prerequisites reach declared outcomes.

Dependencies prevent a job from entering dispatch before its inputs are ready. PostgreSQL stores
the job and its dependency edges in the enqueue transaction, so a rollback removes both.

<Tabs items={["TypeScript", "Python", "Go"]}>
  <Tab value="TypeScript">
    ```ts
    const importId = await queue.enqueue("contacts.import", { source: "upload" });
    await queue.enqueue("contacts.notify", { importId }, { dependencies: {
      prerequisiteJobIds: [importId], onSuccess: "release", onFailure: "fail", onCancellation: "cancel",
    } });
    ```
  </Tab>
  <Tab value="Python">
    ```python
    import_id = queue.enqueue("contacts.import", {"source": "upload"})
    queue.enqueue(
        "contacts.notify",
        {"importId": import_id},
        EnqueueOptions(dependencies=Dependencies(
            prerequisite_job_ids=[import_id],
            on_success="release", on_failure="fail", on_cancellation="cancel",
        )),
    )
    ```
  </Tab>
  <Tab value="Go">
    ```go
    importID, err := queue.Enqueue(ctx, "contacts.import", map[string]any{"source": "upload"}, workhorse.EnqueueOptions{})
    _, err = queue.Enqueue(ctx, "contacts.notify", map[string]any{"importId": importID}, workhorse.EnqueueOptions{
        Dependencies: &workhorse.Dependencies{
            PrerequisiteJobIDs: []string{importID}, OnSuccess: workhorse.DependencyRelease,
            OnFailure: workhorse.DependencyFail, OnCancellation: workhorse.DependencyCancel,
        },
    })
    ```
  </Tab>
</Tabs>

The older `prerequisiteJobId` shorthand is deprecated because it hides the terminal policies.

For fan-in, declare every prerequisite and what each terminal outcome means:

The same `Dependencies` value accepts several prerequisite IDs for fan-in. TypeScript uses
`prerequisiteJobIds`, Python uses `prerequisite_job_ids`, and Go uses `PrerequisiteJobIDs`.

The dependent remains `blocked` until every edge resolves. If policies disagree after fan-in,
`fail` wins over `cancel`, and `cancel` wins over `release`, so concurrent completion order cannot
change the result.

## Inspect blocked work

`Admin.getJob` and `Admin.listJobs` expose `prerequisiteJobIds`, `dependencyPolicy`, and
`blockedReason`. `Admin.getDependencyLineage(jobId)` returns retained edges in both directions,
including policy, resolution, and release evidence. The dashboard exposes the same links and lets
an operator open related tasks directly.

PostgreSQL bounds direct edges and downstream settlement work. A cycle or an oversized graph raises
`DependencyCycleError` or `DependencyLimitExceededError` before unbounded work reaches dispatch.

Producers declare edges with `EnqueueOptions.dependencies`. PostgreSQL stores blocked work in
`job_runtime` until its `runAt` gate and prerequisites allow a `dependency_released` transition.
`Queue.cancel` can settle that graph, while `Queue.health()` reports `Blocked` work through the
public `Queue` client.

## Next

- [Child jobs](/docs/child-jobs) — delegate work from inside a handler
- [Priority](/docs/priority) — order a dependent after release
- [Dead letters](/docs/dead-letters) — preserve terminal evidence

---

Exact dependency schema and lifecycle semantics:
[architecture reference](https://github.com/stablemates/workhorse/blob/main/docs/architecture.md#job_dependency).
