Skip to content

spec: the glue event axis - process-step events and inbound message/file arrivals - #21

Open
delchev wants to merge 1 commit into
mainfrom
spec/glue-event-axis
Open

spec: the glue event axis - process-step events and inbound message/file arrivals#21
delchev wants to merge 1 commit into
mainfrom
spec/glue-event-axis

Conversation

@delchev

@delchev delchev commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Problem

A reacting glue entry (notifications, integrations) could bind only to an entity lifecycle event, and inbound could only be an HTTP webhook. Two things every real application needs were therefore inexpressible:

  1. A moment inside a process — "when this task becomes available, tell the assignee's manager", "when that step completes, call the partner system". Only entity-level changes were observable; the process itself was event-silent.
  2. An arrival that is not HTTP — a record that comes in on a queue or topic, or is dropped into a folder as a file.

Proposed shape

Both extend an existing axis rather than adding a vocabulary.

notifications:
  - name: reviewPending
    event: { onStepReached: { process: LoanApproval, step: librarianReview } }
    to: member.branch.managerEmail
    subject: "Loan {id} is waiting for review"
    body: "A librarian must approve it."

integrations:
  - name: pushActivation
    event: { onStepCompleted: { process: LoanApproval, step: activate } }
    method: POST
    url: "@config:PARTNER_URL"

inbound:
  - { name: leadHook,  path: /webhooks/lead, create: Lead }
  - { name: leadQueue, source: { queue: leads.inbound }, create: Lead }
  - { name: leadFeed,  source: { topic: crm.leads }, create: Lead }
  - { name: leadDrop,  source: { folder: /data/inbox/leads, cron: "0 */5 * * * ?" }, create: Lead }

Expected behaviour (normative, platform-neutral)

Step events. A step event is an event about the record the process runs on — the process's trigger entity — so every action parameter (recipient rule, {placeholder} interpolation, when: guard, forwarded body) resolves exactly as for a lifecycle event; no action needs to know which axis fired it. A conforming generator rejects a binding whose process or step is not declared, whose step is not one that occupies an observable moment (a task, not a decision/wait/end), or whose process has no trigger (there is then no record to be about). onStepReached is observable before the step's own work begins; onStepCompleted after it finished and after that step's writes are persisted, so an observer never sees a stale record. One moment publishes once, however many entries observe it; a jump back into an observed step re-fires its onStepReached observers.

Inbound arrivals. Exactly one arrival per entry: path, or a source naming exactly one of queue / topic / folder. All three save through the entity's ordinary write path — the arrival is a transport, not a second data path, so validations, translations and the create event behave identically. A folder is polled, not watched (hence the mandatory cron, which is an error on the other sources); a file holds one record or an array of them, is not read while still being written, and leaves the drop folder once read — ingested and rejected files kept apart — so nothing is ingested twice and a rejection stays inspectable.

Conversation-shaped transports (acknowledgements, retries with backoff, certificates) stay beyond the scope boundary, as they are today.

Prior art

Handled today by hand-written listener/job code alongside the generated application: a process listener that fetches the record and mails, a queue consumer that parses and saves, a scheduled folder scan. Each is mechanical, and each re-implements the recipient/interpolation rules the format already defines.

…ile arrivals

A reacting glue entry (notifications, integrations) could only bind to an entity
lifecycle event, and `inbound` could only be a webhook. Two things every real
application needs were therefore inexpressible: reacting to a moment IN a process
("when this task becomes available, tell the assignee's manager"), and ingesting a
record that arrives on a queue/topic or as a dropped file.

Both are added without a second action vocabulary:

- `onStepReached` / `onStepCompleted: { process, step }` join the event axis. A step
  event is an event about the record the process runs on - its trigger entity - so
  every recipient path, placeholder, guard and forwarded body reads exactly as it does
  for a lifecycle event. Normative: the process and step must be declared, the step
  must be one that occupies an observable moment (a task), the process must have a
  trigger, a completed step must be observed after its writes are persisted, and one
  moment publishes once however many entries observe it.
- `inbound` declares exactly one arrival: `path` (HTTP), `source: { queue | topic }`,
  or `source: { folder, cron }`. All three save through the entity's ordinary write
  path - the arrival is a transport, not a second data path. A folder is polled, not
  watched, and a read file leaves the drop folder so nothing is ingested twice.
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