Skip to content

Add stable flow aliases through DeployedFlow startup deployment #648

Description

@jumski

Summary

Add stable logical aliases for immutable concrete flow versions through a DeployedFlow wrapper.

Callers start a stable alias such as greetUser. Startup deployment resolves that alias to one concrete version such as greetUserV2. Runs, tasks, queues, broadcasts, and history retain the concrete slug.

Alias metadata remains independent from DAG shape and queue mode.

Dependencies and stage

Public API

Plain flow:

const flow = new Flow<Input>({ slug: 'greetUserV2' })
  .step({ slug: 'greet' }, greet)

export const deployed = defineDeployedFlow(flow, {
  alias: 'greetUser',
})

EdgeWorker.start(deployed)

Private per-step flow:

const routed = withStepQueues(flow)

export const deployed = defineDeployedFlow(routed, {
  alias: 'greetUser',
})

EdgeWorker.start(deployed, { stepSlug: 'greet' })

A plain Flow remains supported and defaults to a self-alias:

alias = concrete flow_slug

defineDeployedFlow() preserves the exact wrapped plain or step-queued flow type. It must not weaken handler, dependency, condition, queue-mode, step-selector, context, or environment inference.

Ownership model

flow_slug
  immutable concrete version and runtime identity

flow_alias
  stable dispatch name with one active concrete target

Flow or StepQueuedFlow
  DAG, handlers, and queue mode

DeployedFlow
  immutable alias membership for one concrete definition

Aliases never appear in generated private queue names. Queue names use the concrete slug so versions remain isolated.

Data model

Add immutable alias membership and one active pointer:

pgflow.flows
  flow_slug primary key
  flow_alias not null
  unique (flow_slug, flow_alias)

pgflow.flow_aliases
  flow_alias primary key
  flow_slug not null
  created_at timestamptz not null
  updated_at timestamptz not null
  foreign key (flow_slug, flow_alias)
    references pgflow.flows (flow_slug, flow_alias)

Invariants:

  • every concrete flow has one immutable alias membership;
  • omitted aliases resolve to the concrete slug;
  • several concrete versions may share one alias;
  • exactly one concrete version is active for each alias;
  • an existing concrete slug cannot move to another alias;
  • existing flows backfill as self-aliases;
  • the migration does not infer version families from slug suffixes.

Startup deployment and activation

Every worker carries the complete wrapped definition. Use one lock order everywhere:

alias-scoped advisory lock
concrete-slug advisory lock

Within one transaction:

  1. register or verify immutable alias membership;
  2. compile or verify the complete concrete flow shape and queue mode;
  3. activate the alias only for a brand-new concrete slug;
  4. commit before worker registration.

Activation rules:

  • a brand-new concrete slug activates after complete compilation succeeds;
  • rechecking the active slug preserves activation;
  • rechecking an inactive slug leaves it inactive;
  • restarting an old worker never reclaims the alias;
  • local destructive recompilation preserves prior activation state;
  • alias mismatch always fails;
  • worker startup failure after activation leaves the alias active and work queued durably.

Several step workers may race to deploy the same new concrete version. The locks make one compile and the others verify.

Two different new versions for one alias must not be rolled out concurrently. The final alias-lock holder would win; document one new concrete version per alias rollout.

Start APIs

Move concrete behavior behind explicit slug functions and make aliases the default:

start_flow()                     alias
start_flow_by_alias()            alias, explicit
start_flow_by_slug()             concrete slug
start_flow_with_states()         alias
start_flow_with_states_by_slug() concrete slug

Compatibility:

  • keep the existing flow_slug RPC argument name on default start functions;
  • backfilled self-aliases keep existing calls working;
  • missing aliases fail and never fall back to concrete lookup;
  • alias resolution and concrete start happen in one database call;
  • returned runs and events expose the resolved concrete slug.

Clients use alias semantics by default and add startFlowBySlug() for pinned starts.

Rollback and deletion

Add:

pgflow.activate_flow_version(flow_alias text, flow_slug text)

It validates membership, takes the alias lock, and atomically switches the pointer. It does not require live workers; durable queues may wait.

Keep deletion concrete-slug based. Rules:

  • deleting an inactive version is allowed;
  • deleting the active target is blocked by default;
  • removing an alias requires that concrete slug to be its sole version;
  • private queues owned by the deleted concrete version follow Persist physical queue identity and ownership for flow tasks #650 deletion rules;
  • future explicit shared queues are never dropped with one version.

Versioned private queues

For a step-queued deployment:

greetUserV1 -> V1 private step queues
greetUserV2 -> V2 private step queues
alias greetUser -> active concrete version for new runs

Existing V1 tasks never move to V2 queues. Keep V1 workers until no V1 run can create or execute more tasks.

Observability

Startup results and logs distinguish:

compilation: compiled | verified | recompiled
activation: activated | active | inactive
alias
active concrete slug
queue mode
selected worker queue

Do not log credentials or message bodies.

Acceptance criteria

  • defineDeployedFlow() preserves exact plain and step-queued flow types.
  • Plain flows retain self-alias behavior.
  • Every concrete flow has immutable, non-null alias membership.
  • Existing definitions backfill as self-aliases without suffix inference.
  • Exactly one active concrete target exists per alias.
  • New concrete definitions compile completely before activation.
  • Concurrent workers for one concrete version compile once and then verify.
  • Active and inactive versions preserve activation on restart.
  • Old-worker restart cannot reclaim an alias.
  • Default start APIs resolve aliases; explicit APIs pin concrete slugs.
  • Runs, tasks, private queues, broadcasts, and history retain concrete slugs.
  • Rollback and concrete deletion follow the stated rules.
  • Startup logs separate compilation, activation, queue mode, and worker subscription.
  • Tests cover backfill, concurrent step-worker startup, rollback, old-worker restart, deletion, missing aliases, and client snapshots.

Out of scope

  • Explicit custom queue names.
  • Queues shared across flows or versions.
  • Multi-flow worker registries.
  • Mutable routing.
  • Cross-worker readiness coordination.
  • Runtime input-schema enforcement.
  • Alias activation history.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions