How Strait tracks job and workflow versions for safe updates and exact run provenance.
Every job and workflow in Strait is versioned. When you update a job's configuration, the previous version is snapshotted so that in-flight runs continue to execute with the config they were enqueued under.
Dual Versioning
Each job and workflow has two version identifiers:
| Field | Type | Purpose |
|---|---|---|
version | Integer | Auto-incrementing counter (1, 2, 3, ...). Used for ordering and internal references. |
version_id | String | Globally unique nanoid (ver_k8f2m9x1p3). Used as the public-facing identifier. |
The integer version is predictable and easy to reason about. The version_id is a 16-character string (4-char prefix + 12-char nanoid using 0-9a-z alphabet) that's safe to expose in URLs and APIs without leaking ordering information.
Version Lifecycle
CreateJob → version=1, version_id=ver_abc123def456
UpdateJob → version=2, version_id=ver_xyz789ghi012 (old config snapshotted)
UpdateJob → version=3, version_id=ver_mno345pqr678 (old config snapshotted)On each UpdateJob, the current configuration is snapshotted to the job_versions table before the update is applied. This means:
job_versionscontains the full config for versions 1, 2, ... (N-1)- The live
jobstable always has the current (latest) version
Atomic Versioning
When a run is enqueued, it captures the job's current version and version_id:
TriggerJob(job_id=X) → run.job_version=2, run.job_version_id=ver_xyz789ghi012When the executor picks up the run, it reads the job config at that version from the snapshot table:
job, err := store.GetJobAtVersion(ctx, run.JobID, run.JobVersion)This prevents a common race condition: if you update a job's endpoint URL while runs are queued, those runs still execute against the old endpoint.
Fallback Behavior
If no snapshot exists for the requested version (e.g., version 1 before snapshotting was implemented), the executor falls back to reading the live jobs table. This ensures backwards compatibility.
Version Policy
Each job and workflow has a version_policy that controls how queued runs interact with version updates:
| Policy | Behavior | Use Case |
|---|---|---|
pin (default) | Runs execute with the version they were enqueued under | Safety-first deployments |
latest | Queued runs upgrade to the latest version at dequeue time | Fast rollouts, non-breaking changes |
minor | Upgrade only if the new version is marked backwards_compatible | Controlled rollouts |
pin (Default)
The safest option. Runs always execute with the exact configuration they were enqueued under. A job update never affects already-queued runs.
Enqueue run (job at v2) → Update job to v3 → Dequeue → Run executes with v2 configlatest
Queued runs automatically upgrade to the current version when dequeued. Useful when you want all runs to use the latest config regardless of when they were enqueued.
Enqueue run (job at v2) → Update job to v3 → Dequeue → Run executes with v3 configminor
A middle ground. Queued runs upgrade only if the new version has backwards_compatible = true. This lets you control which updates are safe for in-flight runs.
Enqueue run (job at v2) → Update job to v3 (backwards_compatible=true) → Dequeue → Run upgrades to v3
Enqueue run (job at v2) → Update job to v3 (backwards_compatible=false) → Dequeue → Run stays at v2Setting Version Policy
curl -X POST https://strait.dev/v1/jobs \
-d '{
"name": "my-job",
"version_policy": "latest",
...
}'Or update an existing job:
curl -X PATCH https://strait.dev/v1/jobs/job_123 \
-d '{"version_policy": "latest"}'Version policy is enforced at dequeue time by the worker dispatch path. pin is still the safest default and is applied automatically when no policy is provided.
Version Snapshots
The job_versions table stores a full snapshot of the job configuration at each version:
endpoint_url,fallback_endpoint_urlmax_attempts,timeout_secstags,payload_schemamax_concurrency,execution_window_cronrate_limit_max,rate_limit_window_secsretry_strategy,retry_delays_secsenvironment_id,group_idenabledversion_id,backwards_compatible
Snapshots are created automatically on UpdateJob. The first version (at creation time) is captured when the first update occurs.
Browsing Versions
List all versions of a job:
curl https://strait.dev/v1/jobs/job_123/versions \
-H "Authorization: Bearer strait_..."Response:
[
{
"id": "ver_abc",
"job_id": "job_123",
"version": 2,
"version_id": "ver_xyz789ghi012",
"endpoint_url": "https://old.example.com/webhook",
"max_attempts": 3,
"backwards_compatible": false,
"created_at": "2025-01-15T10:30:00Z"
},
{
"id": "ver_def",
"job_id": "job_123",
"version": 1,
"version_id": "ver_abc123def456",
"endpoint_url": "https://original.example.com/webhook",
"max_attempts": 5,
"backwards_compatible": false,
"created_at": "2025-01-10T08:00:00Z"
}
]Workflow Versioning
Workflows use the same dual version model (version + version_id) and snapshot workflow definitions, including step graphs.
Useful endpoints:
GET /v1/workflows/{workflowID}/versionsGET /v1/workflows/{workflowID}/versions/{versionID}GET /v1/workflows/{workflowID}/versions/{versionID}/steps
This lets you inspect the exact workflow graph and step configuration used by a historical workflow run.
Run Provenance
Every run records which version it was enqueued under:
{
"id": "run_123",
"job_id": "job_456",
"job_version": 2,
"job_version_id": "ver_xyz789ghi012",
"status": "completed"
}This gives you exact traceability: you can always determine which configuration a run executed with, even if the job has been updated many times since.