# Route Release Pinning (/docs/deployment/release-pinning)



<StoryFrame label="Release invariant" title="The caller names its release. The run keeps that fact for its entire life." accent="pressure">
  <StoryFact label="Admission">
    Exact live deployment identity from the trigger body
  </StoryFact>

  <StoryFact label="Execution">
    Worker build and compatibility set pinned on the run
  </StoryFact>

  <StoryFact label="Retry">
    Same idempotency key resolves the same release-bound run
  </StoryFact>
</StoryFrame>

Worker compatibility prevents the wrong worker from executing a task. Caller release
pinning closes the other half of version skew.

## Configure the runtime identity [#configure-the-runtime-identity]

```yaml
workflow_runtime:
  worker_compatibility:
    mode: enforced
    require_caller_deployment_id: true
    build_id: build-2026-08-25.1
    deployment_id: prod-us-east-1-wave-1
    compatibility_set: workflow-v3
    can_execute_sets:
      - workflow-v3
      - workflow-v2
```

Use a unique build/deployment pair for every release. In enforced mode, the current
compatibility set must appear in `can_execute_sets`.

## Trigger contract [#trigger-contract]

The canonical workflow trigger body accepts `external_deployment_id` (maximum 128
characters). Generic HTTP headers are never consulted. When
`require_caller_deployment_id` is enabled, an absent id fails closed.

Ductor accepts only a server-declared live identity. A building, failed, canceled,
expired, wrong-tenant, wrong-environment, or mismatched identity returns a typed
failure. There is no fallback to whichever code is current and no second pending-run
state machine.

```json
{
  "subject_type": "order",
  "subject_id": "order-4821",
  "idempotency_key": "order-4821-created",
  "external_deployment_id": "prod-us-east-1-wave-1",
  "input": { "order_id": "order-4821" }
}
```

The admitted run persists caller deployment, worker build/deployment, compatibility
set, and shard identity. Child workflows inherit the requirement unless an explicit
governed boundary replaces it.

## Retry and rollout safety [#retry-and-rollout-safety]

Idempotency keys are capped at 2048 bytes at both API and database boundaries. An
exact-key retry resolves the existing run before new-run capacity gates and verifies
the originally pinned deployment, so a saturated tenant can safely retry without
retargeting work.

<ProcessRail label="Rolling release" title="New work advances while old work drains safely">
  <ProcessStep label="01" title="Publish" tone="pressure">
    Declare the new deployment identity and start compatible workers.
  </ProcessStep>

  <ProcessStep label="02" title="Route">
    Send new callers with the new `external_deployment_id`.
  </ProcessStep>

  <ProcessStep label="03" title="Drain" tone="gold">
    Keep old workers alive while their pinned runs remain nonterminal.
  </ProcessStep>

  <ProcessStep label="04" title="Retire" tone="commit">
    Remove the old set only after audit proves the drain is complete.
  </ProcessStep>
</ProcessRail>

Rollback restores the prior build and routes new calls with its live identity. It
does not mutate existing run facts.

<Callout type="warn" title="No recency-based compatibility">
  Ductor never infers that the newest deployment is compatible. Release identity, compatibility set,
  and durable run pins are independent explicit facts.
</Callout>

## Related [#related]

<Cards>
  <Card title="Production Concerns" href="/docs/deployment/production">
    Migrations, immutable images, security posture, and scaling.
  </Card>

  <Card title="Workflows" href="/docs/management/workflows">
    Trigger requests, idempotency, and run control.
  </Card>
</Cards>
