You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add explicit step-to-queue routing and queue-centric workers that safely dispatch several concrete flows or versions from one shared physical queue.
This is a deferred routing stage after private per-step queues prove useful. It reuses the queue identity from #650 and the deployment wrappers from #651 without changing task identity again. If adopted, this issue owns the minimum metadata required for shared queues and explicit adoption.
unknown or missing step keys fail type checking and runtime validation;
queue literals remain available to worker configuration;
several steps may use one queue;
several concrete flows or versions may use one explicit queue;
explicit queues are shared/non-owned resources and are never dropped with one flow;
names receive literal-type validation where possible, synchronous runtime validation, and SQL validation;
routing is immutable for one concrete slug.
The exact helper name may change during implementation, but explicit physical queues must remain separate from withStepQueues() generated private queues.
A batch may contain tasks from several concrete flows. Each returned task includes its concrete flow_slug, step_slug, and immutable queue snapshot.
Validate every (flow_slug, step_slug) pair atomically before task mutation. A flow-only allowlist is insufficient because one flow may route different steps to different queues.
Dispatch
Build a registry keyed by concrete flow slug:
flow_slug -> complete flow handlers and type metadata
For each claimed task:
select the concrete flow;
select the step handler;
build the existing typed input and context;
execute through existing completion and failure paths.
Dependency resolution, retries, task state, and result aggregation remain in the SQL Core.
Message classification and unsupported safety
Use #651's complete pre-claim classification for every message:
exact supported task + queued
-> eligible to claim
exact supported task + started
-> benign duplicate visibility; consume no attempt and defer to recovery
exact supported task + terminal
-> archive idempotently
no task, wrong queue, or unsupported flow-step pair
-> unsupported work
A worker must not partially start a batch containing unsupported work.
Required behavior:
classify the complete batch before mutation or fail the claim transaction atomically;
claim none and reset visibility for the complete batch when any message is unsupported;
consume no attempts;
emit one fatal error with queue, message IDs, and unsupported pairs, but no message bodies;
persistently disable or pause an HTTP worker function to prevent automatic restart loops;
request worker shutdown.
Do not treat visible still-started tasks as corruption. Do not archive, fail, or repeatedly hide unsupported work.
Coverage and version draining
At startup validate coverage for:
every active alias target routed to the queue;
every concrete version with a started run that may later reach the queue;
every executable or recoverable task snapshot on the queue.
Keep old handlers until no started old-version run can reach the queue and no executable or recoverable old-version task remains.
The runtime unsupported-message guard remains necessary after startup because state may change.
Production rollout
Build on #654's in-place enable fence and document the complete shared-worker rollout here:
record and disable every affected queue-worker function;
deprecate live workers and wait until they stop;
deploy every replacement handler registry while functions remain disabled;
include old and new concrete versions during drain;
re-enable only the previously enabled functions.
The first new worker may activate a new alias before another queue worker starts. Durable queues hold work; do not add a second activation protocol.
Shared queue metadata and adoption
#650 intentionally does not prebuild private/shared ownership metadata. If this issue is adopted, add only the persisted marker needed to distinguish an explicitly managed shared queue from a generated private queue. Do not redesign task identity.
Required behavior:
pgflow validates and idempotently provisions a declared shared queue that is absent;
routeFlow() never silently adopts an existing PGMQ queue;
shared workers use the shared-queue SQL boundary, not generic Queue.safeCreate();
queue names are canonical and case-safe;
deleting one flow archives only that flow's active task messages;
deleting one flow never drops an explicit shared queue;
a generated private queue cannot become shared implicitly.
Adopting an existing PGMQ queue is a separate explicit administrative operation. It succeeds only when the queue is empty or every active message has a matching tracked pgflow task identity. Store only the metadata proven necessary for later verification and safe deletion behavior. Resolve exact PGMQ spelling from pgmq.meta instead of duplicating it unless the implementation proves that lookup insufficient.
Acceptance criteria
routeFlow() accepts a complete typed step-to-queue map.
Missing and unknown steps fail type checking and runtime validation.
Queue literals remain available for queueName autocomplete.
withStepQueues() and plain-flow APIs remain source-compatible.
Explicit routes persist through the existing step and task queue identity model.
Explicit queues persist the minimum shared marker needed for verification and are never dropped with one flow.
One queue worker dispatches a complete registry of concrete flows and versions.
Startup rejects incomplete active, draining, executable, or recoverable coverage.
Claims classify queued, still-started, terminal, and unsupported messages before mutation.
Visible still-started tasks consume no attempts and do not stop healthy workers.
Unsupported batches reset visibility, consume no attempts, persistently pause HTTP restart, and stop the worker.
Generic Queue.safeCreate() is not used for shared flow-worker provisioning or adoption.
Summary
Add explicit step-to-queue routing and queue-centric workers that safely dispatch several concrete flows or versions from one shared physical queue.
This is a deferred routing stage after private per-step queues prove useful. It reuses the queue identity from #650 and the deployment wrappers from #651 without changing task identity again. If adopted, this issue owns the minimum metadata required for shared queues and explicit adoption.
Dependencies
Public routing API
Keep
withStepQueues(flow)as the simple private-queue shorthand.Add explicit routing only here:
Rules:
The exact helper name may change during implementation, but explicit physical queues must remain separate from
withStepQueues()generated private queues.Composition with deployment aliases
Flow, routing, and alias metadata remain distinct:Worker API
One worker polls exactly one explicit queue and carries a complete registry:
Another worker can consume another queue:
queueNameautocompletes from the supplied routed deployments.Do not change the APIs from #651:
Startup lifecycle
Before registration and polling:
(flow_slug, step_slug)pairs routed to the selected queue;Compilation and alias activation may commit before worker registration. Durable tasks wait if later startup fails.
Queue claiming
Claim by physical identity and an exact support set:
A batch may contain tasks from several concrete flows. Each returned task includes its concrete
flow_slug,step_slug, and immutable queue snapshot.Validate every
(flow_slug, step_slug)pair atomically before task mutation. A flow-only allowlist is insufficient because one flow may route different steps to different queues.Dispatch
Build a registry keyed by concrete flow slug:
For each claimed task:
Dependency resolution, retries, task state, and result aggregation remain in the SQL Core.
Message classification and unsupported safety
Use #651's complete pre-claim classification for every message:
A worker must not partially start a batch containing unsupported work.
Required behavior:
Do not treat visible still-started tasks as corruption. Do not archive, fail, or repeatedly hide unsupported work.
Coverage and version draining
At startup validate coverage for:
Keep old handlers until no started old-version run can reach the queue and no executable or recoverable old-version task remains.
The runtime unsupported-message guard remains necessary after startup because state may change.
Production rollout
Build on #654's in-place enable fence and document the complete shared-worker rollout here:
The first new worker may activate a new alias before another queue worker starts. Durable queues hold work; do not add a second activation protocol.
Shared queue metadata and adoption
#650 intentionally does not prebuild private/shared ownership metadata. If this issue is adopted, add only the persisted marker needed to distinguish an explicitly managed shared queue from a generated private queue. Do not redesign task identity.
Required behavior:
routeFlow()never silently adopts an existing PGMQ queue;Queue.safeCreate();Adopting an existing PGMQ queue is a separate explicit administrative operation. It succeeds only when the queue is empty or every active message has a matching tracked pgflow task identity. Store only the metadata proven necessary for later verification and safe deletion behavior. Resolve exact PGMQ spelling from
pgmq.metainstead of duplicating it unless the implementation proves that lookup insufficient.Acceptance criteria
routeFlow()accepts a complete typed step-to-queue map.queueNameautocomplete.withStepQueues()and plain-flow APIs remain source-compatible.Queue.safeCreate()is not used for shared flow-worker provisioning or adoption.Out of scope
queue: false) #661.