Skip to content

feat(intent): the glue event axis - process-step events and inbound message/file sources (#6537) - #6743

Merged
delchev merged 1 commit into
masterfrom
feat/intent-glue-event-axis
Aug 15, 2026
Merged

feat(intent): the glue event axis - process-step events and inbound message/file sources (#6537)#6743
delchev merged 1 commit into
masterfrom
feat/intent-glue-event-axis

Conversation

@delchev

@delchev delchev commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Closes #6537.

The declarative glue bound three event kinds — entity lifecycle, cron, inbound HTTP. Two commonly needed sources were missing, and the issue asks for both, reusing the existing action vocabulary.

1. Process-step events

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"

"Reusing the action vocabulary" is taken literally: no second delivery mechanism is added. BpmnIntentGenerator inserts a generated JavaDelegate at the step's boundary — before the step for reached, after the writer/setter for completed and carrying the step's next so routing cannot be bypassed — which loads the process's trigger entity by the id in the clear-D context and publishes its JSON on the entity's own topic plus a step suffix (-step-<process>-<step>-reached|completed), deferred to after commit so a consumer never sees the record mid-chain. The notification/integration listeners are therefore generated unchanged: every recipient path, {placeholder}, when: guard, attach: print and forwarded body reads exactly as it does for a lifecycle event.

StepEventSupport owns the whole translation (binding → entity → topic suffix → emitter) and deduplicates per (process, step, moment), so ten observers of one moment still publish once. EventBinding stays lifecycle-only.

Rejected at parse, each naming the cause: an unknown process or step; a step kind with no observable moment (only a userTask/serviceTask has one — not a decision, a wait or the end); a process with no trigger (there is then no record the event could be about).

2. Inbound message / file sources

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

An entry declares exactly one arrival: a path, or a source naming exactly one of queue / topic / folder. What each generates under gen/events/<module>: the existing @Controller webhook; a self-describing MessageHandler (destination()/kind()); a self-describing JobHandler (cron()) polling the drop folder. All three save through the same generated repository — the arrival is a transport, not a second data path, so validations, i18n and the create event fire identically.

The file ingest is specified, not improvised: a file holds one record or an array of them; a file modified inside the stability window is left for the next tick (it may still be being copied in); every read file is moved into processed/ or failed/, so nothing is ingested twice and a rejected file stays inspectable. A folder is polled, not watched — hence the mandatory cron there and its rejection on the other sources.

Kafka/RabbitMQ sources are deliberately out: the platform has no declarative consumer bean for them, and the source: map is open for one when it exists.

Tests

  • UnitGlueStepEventsTest (one emitter per moment; the consumers bound to the step topic of the trigger entity; a lifecycle binding unchanged), StepEventBpmnTest (the emitter's insertion points and routing in the rendered BPMN), GlueInboundSourcesTest (each arrival routed to its own collection with its coordinates), GlueEventAxisIntentTest (every parse rejection of both halves).
  • Both layers of the emission contract, in IntentEmissionCoverageIT as the module guide requires: the generated tokens (the emitter wired into the flow, its topic, the consumers binding to that exact topic, the consumer/job shapes), and the published app — a record sent to the declared queue and a batch file dropped into the polled folder both turn into rows over REST, and the step-reached emitter now sits inside the RFQ flow whose task the existing timeout/expire/wait scenarios drive, so a broken emitter fails them.

Verified locally: IntentEmissionCoverageIT green (H2), engine-intent + ide-template unit suites green, formatter:validate and the release-profile javadoc clean.

Docs

In-repo: the assistant guide (intent-assistant-guide.md), the module guide and the root guide. Separately: the vendor-neutral spec (IntentFile/intent-specification#21, IntentFile/intentfile.github.io#21) and dirigible-io/dirigible-io.github.io#184 (merged).

…essage/file sources (#6537)

The declarative glue could only observe entity lifecycle events, and an `inbound`
could only be an HTTP webhook. Two things every application needs were therefore
inexpressible: reacting to a moment INSIDE a process ("when this task becomes
available, tell the assignee's manager", "when that step completes, call the partner
system"), and ingesting a record that arrives on a queue/topic or as a dropped file.

Process-step events. A notification/integration `event:` now also takes
`onStepReached` / `onStepCompleted: { process, step }`. This adds no second delivery
mechanism, which is the whole point: the BPMN generator inserts a generated
JavaDelegate at the step's boundary - before the step for reached, after the
writer/setter for completed, carrying the step's `next` so routing cannot be bypassed
- which loads the process's trigger entity by the id in the clear-D context and
publishes its JSON on the entity's own topic plus a step suffix, deferred to after
commit. The consumers are generated unchanged, so every recipient path, placeholder,
guard, print attachment and forwarded body reads exactly as for a lifecycle event.
`StepEventSupport` owns binding -> entity -> topic -> emitter, deduplicated per
(process, step, moment) so ten observers still publish once. Rejected at parse: an
unknown process/step, a step kind with no observable moment (only userTask and
serviceTask have one), a process with no trigger (there is then no record to be about).

Inbound arrivals. `inbound[]` declares exactly one arrival: `path:` (the existing
@controller webhook), `source: { queue | topic }` (a self-describing MessageHandler),
or `source: { folder, cron }` (a JobHandler polling the drop folder: one record or an
array per file, a file touched within the stability window left for the next tick,
every read file moved into processed/ or failed/ so nothing is ingested twice and a
rejection stays inspectable). All three save through the same generated repository -
the arrival is a transport, not a second data path. A folder is polled, never watched,
hence the mandatory cron there and its rejection elsewhere.

Tests: unit coverage of the emitter collection, the consumers' topic binding, the BPMN
insertion points and every parse rejection; and both layers of the emission contract in
IntentEmissionCoverageIT - the generated tokens, plus the published app really ingesting
a record sent to the queue and a batch file dropped into the folder, with the
step-reached emitter now sitting inside the RFQ flow whose task the existing scenarios
drive.

Docs: the assistant guide, the module guide and the root guide; the spec and the two
doc sites are separate PRs.
@delchev
delchev merged commit eeae507 into master Aug 15, 2026
10 checks passed
@delchev
delchev deleted the feat/intent-glue-event-axis branch August 15, 2026 12:12
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.

intent: glue event axis - process-step events and inbound message/file sources

1 participant