Repository navigation
Conversation
The Camunda 7 / 8 engine rules need the ./engine, ./camunda-7 and ./camunda-8 entry points of Miragon/bpmnlint-rules#113, which is not released yet. Swap the tarball for the released version before merging.
The W_C7_* / W_C8_* checks no longer come from the domain's own profiles (domain/validation/profiles). The domain asks an EngineLint port; the bpmnlint adapter answers it with camunda-compat's config for the file's engine version plus bpmn-cli's own rules for what camunda-compat leaves out. The own rules live in adapters/out/bpmnlint/engine/rules/c7|c8, are written on EngineRule of @miragon/bpmnlint-plugin-rules/engine and run through its camunda7 / camunda8.withRules. Each engine loads as a chunk of its own. The findings keep their codes and hints: the own rules give the hint with their report, camunda-compat findings get one from where they point (EngineHint, CompatHints). Where camunda-compat reads a file differently than Camunda 8.9 (engine-checked), CompatVerdicts follows the engine. The profile tests of main pass unchanged apart from the intended differences listed in docs/camunda.md (camunda-compat's wording, findings reported on the flow or target instead of the gateway, W_C8_DEPLOY_UNSUPPORTED for an event without definition). The on-demand chunk budgets grow (C7 127 KB, C8 367 KB, engine run 49 KB minified) for the hint texts and the camunda-compat rules; the entry budgets are unchanged. BREAKING CHANGE: loadProfile, loadProfiles, runProfile and ProfileReport are no longer exported; validateDoc(doc) runs the structure alone, the engine rules run in checkDoc, mutateDoc and the in-memory API.
…per run EngineRun lints with the plugin's EngineLint.lint, which visits every element once for all rules instead of once per rule; a rule that throws comes back as its rule-error, so the per-rule guard goes. DocumentNamespaces walks the whole document: it is read once per run and handed to the checks instead of eight times. Engine time on a 1,800-node model drops from about 1.0 s to 0.55 s (C8) and 0.65 s to 0.45 s (C7). The Camunda 7 / 8 chunk budgets grow by about 5 KB / 1 KB gzip: bpmnlint's linter, which resolves the configs, is now imported by the rules chunk.
…int(root, rules) The plugin no longer resolves configs or takes the host's rules: bpmn-cli builds the rule list itself, camunda-compat's rules for the engine version plus its own, each own rule at the category of its impact, and runs it in one walk. The severity overrides become categories or a left-out rule.
…one report helper The 39 Camunda 7 rules become six group rules (c7-events, c7-flows, c7-implementation, c7-values, c7-extension-content, c7-vendor-content), each with one dispatch per element. Every report carries the former rule name as data.finding; a rule declares its findings with code and impact, so codes and severities come from the declaration and EngineCodes keeps only the camunda-compat table. CamundaRule.report builds the camunda-compat report shape once (node, parentNode, finding). Shared helpers: EngineScope (where the engine runs content), SetCommand (the bpmn set / ext address), Values, FirstOfKey (first of a key). The C7 helpers collapse into C7Host (with the extension hints and element tree), C7Diagram (with the unresolved references), C7EventDefinitions, ImplementationHost and CamundaPlacement. The C8 rules only declare their single finding; EngineRun and CompatVerdicts select by finding. The rule specs are regrouped per group rule and assert per finding. Findings are identical: same tests pass and fail as before, and `bpmn validate --platform c7|c8 --json` gives byte-identical output on all 176 .bpmn files of the repository. Lines (wc -l): - engine adapter: 12,357 -> 9,047 - rules/c7: 5,537 -> 2,149 - rules/ shared files: 287 -> 416 - c7 rule specs: 4,736 -> 4,696
…hare the vendor-content checks The 22 Camunda 8 rules become six group rules (c8-events, c8-subscriptions, c8-extensions, c8-vendor-content, c8-feel-expression, c8-schema) on CamundaRule and its report helper; every report keeps its former rule name as data.finding, so codes, severities and the parity tests stay. Shared with Camunda 7: VendorHost (where a host's findings land, its path, its bpmn set/ext address and its vendor element tree, now the base of C7Host and C8Host), VendorContentRule (the foreign-content, unknown and misplaced attribute and extension findings, worded by an engine phrase table, the fixes per engine), ModdlePath, SetCommand.at, EngineScope.C8 and Values. The C8 helpers collapse into C8Host, C8EventDefinitions (checked definition, message holders, used messages) and ZeebeHint (ZeebeFixHint merged in). ZeebeSchema is gone and CamundaDescriptor is a thin adapter: the rules read kit.ZEEBE and kit.CAMUNDA. The rule specs parse with the plugin's moddle extensions. c8-schema drops its unprefixed-attribute branch: UnprefixedAttributes reports every such attribute, and EngineRun's filter only caught the copy on the element itself, so an attribute of an event definition, loop or condition was listed twice. That filter is gone too. `bpmn validate --platform c7|c8 --json` gives byte-identical output on all 176 .bpmn files of the repository; the same 8 start-event tests fail as before (plugin #113 names bpmnlint/start-event-required). Lines (wc -l): - engine adapter: 9,047 -> 6,449 - rules/c8: 4,832 -> 2,302 - rules/c7: 2,149 -> 1,855 - rules/ shared files: 416 -> 644 - c8 rule specs: 3,334 -> 3,278
… shrink the engine glue EngineRun now only orchestrates: parse, lint, verdicts, findings. CompatVerdicts holds every place bpmn-cli reads camunda-compat differently: the severity overrides (rules()), the processes the engines do not run, the send tasks a zeebe:publishMessage implements and the Camunda 8 verdicts (apply()). The loop-input filter was no camunda-compat correction but one of bpmn-cli's own c7-multi-instance: the rule now reads a loopDataInputRef the parse dropped from C7Diagram's unresolved references, which the run records from the same import warnings. Glue clones removed: - CompatHints merged into EngineHint (one xmlName), which quotes with sh. - CamundaBooleans, PublishMessages, UnknownVendorElements and UnprefixedAttributes extend OwnModelCheck (kit, doc, namespaces, engine, the executable elements) and address and name elements with SetCommand and describeElement instead of private copies; UnknownVendorElements guesses with didYouMean (Levenshtein, as main's profiles did) and asks the rules' typed parse whether it knows a zeebe type instead of reading ZEEBE_DESCRIPTOR, which leaves the engine kit. - The NCName regex lives once, in Values.isNcName. `bpmn validate --platform c7|c8 --json` gives byte-identical output on all 176 .bpmn files of the repository; the same 8 start-event tests fail as before (plugin #113 names bpmnlint/start-event-required). Lines (wc -l): - engine adapter: 6,449 -> 6,323 - glue (engine/*.ts): 1,648 -> 1,514
…le chunk budgets docs/camunda.md lists the group rules per code (bpmn-cli/c7-events, bpmn-cli/c8-subscriptions, ...) and says how the checks run now: one walk with the plugin's lint, camunda-compat's compatRules, six own rules per engine that declare their checks. CONTRIBUTING.md names EngineFindings, CompatVerdicts and OwnModelCheck instead of CompatHints. docs/library.md gets the measured bundle sizes. tools/iso/budget.json, rounded up to the next whole KB: - Camunda 7 rules: 134 / 37 KB -> 117 / 34 KB (116.4 / 33.2 KB) - Camunda 8 rules: 374 / 104 KB -> 361 / 101 KB (360.2 / 100.7 KB) - engine run: 50 / 15 KB -> 49 / 17 KB (48.6 / 16.1 KB) The engine run has been over its 15 KB gzip budget since bd576eb, which moved bpmnlint's rule running into the plugin's lint() and reads the namespaces once per run (15.8 KB gzip at f6e5df0). The glue now shares describeElement, didYouMean, SetCommand, sh and Values with the rule chunks instead of private copies, which counts them in the engine chunk too: minified it shrinks (49.2 -> 48.6 KB), gzip grows by 0.25 KB. The whole entry and the applyToXml-only entry stay within their budgets (1073.0 / 337.0 and 776.1 / 242.6 KB).
The rule files carried their bpmnlint rule id (c7-events.ts) next to helper files named after their class (C7EventDefinitions.ts). Every file is now named after its class in PascalCase: C7Events.ts, Camunda7Rules.ts (was index.ts), and the specs follow (C7Events.test.ts). The rule ids stay in the declarations. describe.ts, shell.ts and didYouMean.ts held free functions: they are the classes ElementKinds (describe, trigger), Shell (quote, slotted) and DidYouMean (of).
Each rule header now says what it checks, what the engine does with such a file, one bullet per reported finding in findings-map order, and an XML example where the mistake is not visible in the diagram. C7Implementation now declares the c7-bad-value finding it reports (empty external topic) in its findings map.
The own engine rules reported problems that bpmn-cli's own writes rule out, or that bpmn-cli has no context for: - c7-boundary-host, c8-boundary-host: always repeated by the structural E_INVALID_HOST; StructuralValidator.withProfile no longer filters them - c7-multiple-event-definitions: the CLI keeps one definition per event - c7-definitions-extension, c8-schema (the whole rule): ext add refuses bpmn:definitions, and the CLI writes NCName ids - c7-duplicate-extension, c7-async-duplicate-extension: ext add merges or refuses a second single-instance element - c7-empty-condition and the empty topic / calledElement of c7-bad-value: set removes empty values and refuses empty conditions - the schema checks of the file's text (SchemaText, SchemaTextFindings, BpmnSchema): child order, IDREFs, xsd:booleans, unreadable ids - c8-event-definition: several definitions, a definition without id; c8-event-gateway: fewer than two listed as outgoing; c8-extension: a header key twice in a second header list c8-duplicate-extension stays: without it, camunda-compat's reports on the doubled element come back as new findings. The iso budgets drop to the new sizes, rounded up to the next whole KB. BREAKING CHANGE: validate no longer reports W_C7_DEPLOY_BOUNDARY_HOST, W_C8_DEPLOY_BOUNDARY_HOST, W_C7_MULTIPLE_EVENT_DEFINITIONS, W_C7_DEPLOY_DEFINITIONS_EXTENSION, W_C7_DEPLOY_DUPLICATE_EXTENSION, W_C7_DUPLICATE_EXTENSION and W_C7_EMPTY_CONDITION, and reports W_C8_DEPLOY_SCHEMA only for unprefixed attributes.
… the JUEL checks catch first
…namespace prefix among the engine rules
…d EngineFindings defaults
…upport helpers Move EngineRun, EngineFindings, EngineCodes, EngineHint and CompatVerdicts into engine/run/, give every rule the suffix Rule (C7EventsRule, C8FeelExpressionRule, ...), and move the helpers of each engine into rules/c7/support/ and rules/c8/support/, the shared ones into rules/support/. CamundaRules.ts becomes CamundaRule.ts. Rule ids and finding names stay.
…n the file as written UnprefixedAttributes, CamundaBooleans, PublishMessages and UnknownVendorElements become bpmnlint rules run in the same lint call (c7/c8-unprefixed-attributes, c7-camunda-booleans, c8-publish-message, c7/c8-unknown-vendor-elements); OwnModelCheck goes away. The typed parse drops or rewrites what they check and its warnings name no element, so the engine run records the file as written (WrittenModel): a rule reads the written twin of each element it visits and reports on the typed one. Their findings keep the old order and do not drop camunda-compat subjects. The rule chunks grow by what moves out of the engine run: budgets c7 109/32 -> 119/35 KB, c8 355/99 -> 366/102 KB, engine 38/14 -> 27/11 KB.
…o the structural check, as on main
emaarco
force-pushed
the
emaarco/refactor-move-the-camunda-7-8-engine-rules-out-o
branch
from
October 29, 2026 07:09
9fd4e0c to
59e373f
Compare
Each of the nine group rules (C7EventsRule, C7FlowsRule, ...) reported several findings, each bundling several checks. Now one file holds one rule with one check, named after what must hold (c7-message-needs-name, C7MessageNeedsNameRule): seventy rules, with small abstract bases where two or more share a walk (C7ExecutableRule, C7EventReferenceRule, C7ListenerRule, ...). The vendor element base splits into the unknown element rule and the C7/C8 placement rules. Codes, messages, hints, paths and the order of the findings stay as they were; one spec per rule, split from the old specs.
The six group rules (C8EventsRule, C8SubscriptionsRule, ...) become fifty rules with one check each, named after what must hold (c8-error-code-unique, C8ErrorCodeUniqueRule), sharing walks in small abstract bases (C8FeelRule, C8HostRule, C8SubscriptionUniqueRule, C8PublishMessageRule, ...). CompatVerdicts reads the publishMessage and duplicate extension rules by their new names. Codes, messages, hints, paths and the order of the findings stay as they were; one spec per rule, split from the old specs, and a spec for c8-timer-value-valid.
With one check per rule the findings map and data.finding go: defineRule takes the rule's code and impact and derives kind and engine from the name, and EngineFindings maps a report by its rule. docs/camunda.md and CONTRIBUTING list the new rules and their naming scheme. The rule chunks grow with the per-rule modules: the budgets rise by 8 KB / 1 KB (C7) and 4 KB / 1 KB (C8), minified / gzip.
This branch has not been deployed
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.
Why
#114 asked whether bpmn-cli's own engine profiles (
src/domain/validation/profiles/, ~4,250 lines) could shrink by leaving the checks camunda-compat already covers to camunda-compat and running the rest as bpmnlint rules on the framework of@miragon/bpmnlint-plugin-rules. This branch tries it end to end, so the trade-off is measured instead of guessed.What
src/adapters/out/bpmnlint/engine/rules/c7|c8/, one rule per file. They run together with camunda-compat's rules for the file's engine version in the plugin's single-passlint(root, rules)(feat: per-version camunda-compat rules and a single-pass lint for a host's engine rules bpmnlint-rules#113). The adapter has no checks outside that call. TheW_C7_*/W_C8_*codes and hints stay.engine/run/turns reports into findings (EngineRun,EngineFindings,EngineCodes,EngineHint,CompatVerdicts).engine/rules/holds the base classCamundaRule, the rules (*Rule) and their helpers insupport/folders. Every file is named after its class, and each rule's header says what it checks, why, and gives an example.<engine>-<subject>-<requirement>(e.g.c7-message-event-needs-message, classC7MessageEventNeedsMessageRule) and declares one code and impact viadefineRule({ ruleName, code, impact }, Class). Several rules may share a code. Shared logic lives in abstract bases only where at least two rules use it (C7ExecutableRule,C7EventReferenceRule,C7ListenerRule,C8FeelRule,C8HostRule,C8PublishMessageRule,VendorElementsRule, …). The naming scheme is in CONTRIBUTING.bpmn:definitions, duplicate Camunda 7 extensions, empty conditions and values, and the schema text checks (child order, NCName ids, xsd booleans).c7-/c8-boundary-hostare dropped because the structuralE_INVALID_HOSTalways reports the same element.zeebe:publishMessageare rules too. The typed parse drops or rewrites what they look at, so they read the file as written throughWrittenModel, whichEngineRunrecords per run.bpmnlint/start-event-required, the name its reports carry, so a missing start event getsW_C8_DEPLOY_START_EVENTagain.history-time-to-live), so the Camunda 7 checks had to be ported; for Camunda 8 it fully covers about 11 codes. The glue that maps camunda-compat's reports and the bridge to the file as written are new.Verification
main, are the parity check; cases of the dropped findings are removed and listed indocs/camunda.md.bpmn validate --jsonfor c7 and c8 over all 164.bpmnfiles of the repository changed only by the dropped findings and the start-event fix, and stayed byte-identical through the helper cleanup, the restructuring and the split into single-check rules.npm run gatepasses locally (tsc, biome, knip, jscpd, vitest 3,403 tests,tools/iso/check.mjs, bench regress, fuzz). The plugin is installed from a locally packed tarball, so CI cannot install it.wc -l): engine adapter 8,755 (run 839) against 4,249 for the profiles onmain; 5,701 with the 17 grouped rules before the split. Rule specs: 12,104 (109 spec files). The on-demand bundle budgets grew: C7 126 KB / 36 KB gzip (was 119 / 34), C8 369 KB / 103 KB gzip (was 365 / 102).main, measured before the last cleanup (not re-measured):mainbpmn setandbpmn ext addcan write any vendor content, so nearly every rule can fire after an edit, and running only some rules after an edit is not safe.Breaking changes
loadProfile,loadProfiles,runProfileandProfileReportare no longer exported.W_C7_DEPLOY_BOUNDARY_HOST,W_C8_DEPLOY_BOUNDARY_HOST,W_C7_MULTIPLE_EVENT_DEFINITIONS;W_C7_DEPLOY_SCHEMA/W_C8_DEPLOY_SCHEMAnow only for unprefixed attributes, no longer for child order, ids or booleans in the file text.Refs #114