Skip to main content

End-to-end: inventory guard

Advanced ⏱ 20 min retail · end-to-end · keyed aggregate

You will build: the live inventory pipeline from the Retail use case — a continuously updated position per SKU fed by orders and restocks, with a low-stock rule firing before the oversell.

orders + restocks → streaming:position (keyed aggregate) → rule-based:guard → webhook (ops)

Prerequisites

  • The quickstart completed. No LLM needed — this pipeline is fully deterministic.

1 · Scaffold

pulse new inventory --source webhook \
--stage streaming:position --stage rule-based:guard --sink webhook

2 · The position stage — one running total per SKU

stages:
- name: position
engine: streaming
operators:
- type: aggregate
key: sku
aggregations:
sold: "sumWhere(type == 'order')"
received: "sumWhere(type == 'restock')"

Unlike a window, a keyed aggregate keeps a continuous running state per key — exactly what an inventory position is.

3 · The guard stage

- name: guard
engine: rule-based
rules:
- { condition: "received - sold < 10", action: flag }

4 · The sink

pulse secret set OPS_WEBHOOK https://webhook.site/<your-id>
sink:
kind: webhook
url: ${secret:OPS_WEBHOOK}

5 · Deploy and run a sale day

pulse secret set PULSE_WEBHOOK_SECRET dev-secret # the scaffolded webhook source requires it
pulse deploy .
pulse events publish --topic inventory.in --value '{"sku":"tee-black-m","type":"restock","qty":1,"amount":12}'
for i in $(seq 1 8); do
pulse events publish --topic inventory.in --value '{"sku":"tee-black-m","type":"order","qty":1,"amount":1}'
done
pulse events tail --topic inventory.guard.out

As orders eat into the position, the moment received - sold crosses the safety threshold the guard fires — and your ops webhook receives the low-stock alert while the SKU is still sellable, not in tomorrow's sync report.

6 · Prove per-SKU isolation

pulse events publish --topic inventory.in --value '{"sku":"mug-blue","type":"order","qty":1,"amount":1}'

mug-blue has its own independent position — tee-black-m's numbers don't move.

What just happened

The nightly inventory sync became a subscription: every order and restock updates one keyed state the moment it happens, and every channel that needs the truth reads the same topics (Call your app from anywhere). The "oversold because the channels disagreed for six hours" failure mode has no room to exist.

Troubleshooting

The guard never fires

Check inventory.position.out — the running sold/received totals must actually cross your threshold. Field names in the rule must match the aggregation names exactly.

Next