Skip to content

feat(pathways): pause and resume delivery from the control plane - #112

Merged
jbiskur merged 2 commits into
mainfrom
t3code/pathways-remote-control-support
Sep 15, 2026
Merged

jbiskur merged 2 commits into
mainfrom
t3code/pathways-remote-control-support

Conversation

@jbiskur

@jbiskur jbiskur commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Blocked on a dependency publish. This needs @flowcore/data-pump@0.23.0, which is tagged on GitHub but not on npm. See the blocker note at the bottom.

Summary

Adds datapumpPause and datapumpResume to the poll-based command queue, and makes the pause durable so a pathway comes back paused at the same position after a restart.

A paused pump keeps fetching, keeps its buffer, keeps its cursor and keeps reporting pulses. Only delivery to the handlers stops, so the control plane still sees a live pathway. A batch already inside a handler finishes and acknowledges.

Targeting needs no new control-plane field

All three commands share one target list, carried in the existing sourceFlowTypes:

Entry Targets
"orders.0" Every pump group on that flow type
"orders.0::hot" That one pump only
empty or absent Every pump

The composite form is already public API — it is the concurrency.byPumpGroup key and the value reset() returns. A pump group belongs to exactly one flow type, so the pair is the identity and one field carries both levels of precision.

Three gaps fixed along the way

  • datapumpRestart silently dropped pump groups. It passed only sourceFlowTypes to reset(), though PumpResetFilter already accepted pumpGroups. Restart now shares the pause filter.
  • stopAt was parsed and discarded. It now reaches FlowcoreDataPump.restart(state, stopAt).
  • Unknown command types reported success. The handler logged a warning and the poller then posted running, so the control plane recorded a success that never happened. Unknown types and filters matching no pump now throw and report failed.

Validation

The poll response is validated with Zod instead of cast. Everything downstream acts on a live pump: a malformed position would move a cursor, a malformed target list would pause the wrong pathway.

Durability

New PathwayDeliveryStore, configured with withPathwayDeliveryStore(). The default is in memory; PostgresPathwayDeliveryStore survives a redeploy, a pod restart and a cluster leader change. The stored pause is applied before the pumps start, so a restored pathway never delivers a batch on the way up.

Test plan

  • CI green
  • @flowcore/data-pump bumped to ^0.23.0 (blocked, see below)
  • 78 tests / 236 steps pass locally with Postgres running
  • deno fmt --check, deno lint, deno check src/mod.ts clean
  • 36 new steps across three new test files

New tests cover: target parsing, pause/resume by flow type and by pump group, union targeting, empty-match handling, idempotence, restart honouring pump groups, stopAt pass-through, unknown-type failure, persistence on pause and resume, a store write failure failing the command, and the Postgres store surviving a new instance.

Blocker

This PR must not merge until @flowcore/data-pump@0.23.0 is on npm. The library calls pump.pause() and pump.resume(), which do not exist in the currently resolved 0.22.1.

The publish is broken in the data-pump repo, and has been since 0.22.1 — 0.22.2, 0.22.3 and 0.23.0 are all tagged on GitHub and absent from npm:

  1. The publish job pinned node 20 and ran npm install -g npm@latest, failing with EBADENGINE — fixed in fix(ci): publish to npm on node 22 data-pump#94.
  2. A failed publish could not be re-run — fixed in fix(ci): allow the npm publish to be dispatched manually data-pump#96.
  3. npm auth. The publish now reaches npm publish and fails with E404 Not Found - PUT https://registry.npmjs.org/@flowcore%2fdata-pump. The package exists, so a 404 on PUT means the token is expired or no longer has publish rights on the @flowcore scope. This needs a maintainer to rotate NODE_AUTH_TOKEN.

jbiskur and others added 2 commits September 15, 2026 11:01
Adds `datapumpPause` and `datapumpResume` to the poll-based command queue, and
makes the pause durable so a pathway comes back paused at the same position after
a restart.

`PathwayPump.pause()` / `resume()` stop delivery to the handlers while the pumps
keep fetching, keep their buffer, keep their cursor and keep reporting pulses.
Requires @flowcore/data-pump 0.23.0.

Targets are carried in the existing `sourceFlowTypes` field. A bare `"orders.0"`
targets every pump group on that flow type, a composite `"orders.0::hot"` targets
exactly one pump. That composite form is already public API — it is the
`concurrency.byPumpGroup` key — so pump groups need no new control-plane field.

Three gaps are fixed along the way:

- `datapumpRestart` silently dropped pump groups. It passed only `sourceFlowTypes`
  to `reset()`, even though `PumpResetFilter` already accepted `pumpGroups`.
- `stopAt` was parsed off the command and then discarded. It now reaches
  `FlowcoreDataPump.restart(state, stopAt)`.
- An unknown command type logged a warning and the poller then reported `running`,
  so the control plane recorded a success that never happened. Unknown types and
  filters that match no pump now throw, and are reported as `failed`.

The poll response is validated with Zod instead of cast. Everything downstream
acts on a live pump, so a malformed `position` would move a cursor and a malformed
target list would pause the wrong pathway.

Durability comes from a new `PathwayDeliveryStore`, configured with
`withPathwayDeliveryStore()`. The default is in memory; `PostgresPathwayDeliveryStore`
makes it survive a redeploy, a pod restart and a cluster leader change. The stored
pause is applied BEFORE the pumps start, so a restored pathway never delivers a
batch on the way up.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
0.23.0 adds `pause()`, `resume()` and the `paused` construction option that
PathwayPump now calls. On 0.22.1 those methods do not exist, so a pause command
would throw at runtime.

0.22.1 also predates the April 2026 lifecycle fix, where a restart resumed the
fetch loop but neither the process loop nor the pulse emitter. Pause is a
lifecycle change, so it would have wedged the same way.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jbiskur
jbiskur merged commit 4af2989 into main Sep 15, 2026
2 checks passed
@jbiskur
jbiskur deleted the t3code/pathways-remote-control-support branch September 15, 2026 12:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant