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 the general routing stage after private per-step queues prove useful. It extends the queue identity and ownership model from #650 and the deployment wrappers from #651 without changing task identity again.
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
Use #654's fence around every affected queue worker function:
record and disable the affected functions;
deprecate live workers and wait until they stop;
deploy every replacement 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.
Queue ownership and adoption
Explicit routed queues use the shared/non-owned registry state introduced by #650:
pgflow validates and idempotently provisions a declared queue that is absent;
routeFlow() never silently adopts an existing unregistered PGMQ queue;
shared workers use the registry-aware SQL path, 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 queue.
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. It records exact PGMQ metadata spelling and shared ownership before routed flow compilation can reference the queue.
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 register as shared/non-owned 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 registration.
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 the general routing stage after private per-step queues prove useful. It extends the queue identity and ownership model from #650 and the deployment wrappers from #651 without changing task identity again.
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
Use #654's fence around every affected queue worker function:
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.
Queue ownership and adoption
Explicit routed queues use the shared/non-owned registry state introduced by #650:
routeFlow()never silently adopts an existing unregistered 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. It records exact PGMQ metadata spelling and shared ownership before routed flow compilation can reference the queue.
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 registration.Out of scope