feat(intent): the glue event axis - process-step events and inbound message/file sources (#6537) - #6743
Merged
Merged
Conversation
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
"Reusing the action vocabulary" is taken literally: no second delivery mechanism is added.
BpmnIntentGeneratorinserts a generatedJavaDelegateat the step's boundary — before the step for reached, after the writer/setter for completed and carrying the step'snextso 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: printand forwarded body reads exactly as it does for a lifecycle event.StepEventSupportowns the whole translation (binding → entity → topic suffix → emitter) and deduplicates per (process, step, moment), so ten observers of one moment still publish once.EventBindingstays lifecycle-only.Rejected at parse, each naming the cause: an unknown process or step; a step kind with no observable moment (only a
userTask/serviceTaskhas one — not a decision, a wait or the end); a process with notrigger(there is then no record the event could be about).2. Inbound message / file sources
An entry declares exactly one arrival: a
path, or asourcenaming exactly one ofqueue/topic/folder. What each generates undergen/events/<module>: the existing@Controllerwebhook; a self-describingMessageHandler(destination()/kind()); a self-describingJobHandler(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/orfailed/, so nothing is ingested twice and a rejected file stays inspectable. A folder is polled, not watched — hence the mandatorycronthere 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
GlueStepEventsTest(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).IntentEmissionCoverageITas 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:
IntentEmissionCoverageITgreen (H2), engine-intent + ide-template unit suites green,formatter:validateand 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).