Skip to content

feat(flow): a decision-table node puts rule evaluation into the graph - #3329

Merged
rubenvdlinde merged 2 commits into
developmentfrom
feature/flow-decision-tables
Sep 2, 2026
Merged

feat(flow): a decision-table node puts rule evaluation into the graph#3329
rubenvdlinde merged 2 commits into
developmentfrom
feature/flow-decision-tables

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Summary

OpenSpec change flow-decision-tables: a new node type openregister.decision-table puts rule evaluation into the flow graph. It evaluates a DMN-style decision table against each item through the shared evaluator from shared-decision-table-evaluator and writes the outputs onto configured item fields. dossiq is staging the retirement of its app-side decision tables against this node, per the directive that apps get no flow engines of their own.

What changed

  • lib/Service/Flow/Nodes/DecisionTableNode.php: the node. Implements IFlowNode, IFlowNodeConfigKeys and IFlowNodeConfigForm. Config keys: table (inline definition), inputMapping, outputMapping (name to dotted path, same-name default, dossiq's handler vocabulary kept verbatim), defaultOutputs (explicit no-match row), resultKey (optional evaluation record). Deterministic, per item, never suspends.
  • lib/Service/Dmn/DecisionTableValidator.php: save-time validation that probes every rule cell through UnaryTestEvaluator::matches(), so the accepted grammar is the executable grammar by construction. Structural refusals: unimplemented hit policy by name, empty inputs/outputs/rules, duplicate column names, positional count mismatches, non-integer priority.
  • lib/Service/Dmn/DecisionTableEvaluator.php: exposes effectiveType() and makes IMPLEMENTED_HIT_POLICIES public so validator and evaluator cannot drift. No behaviour change; the existing evaluator suite stays green.
  • lib/Listener/FlowNodeRegistrationListener.php: registers the node as the 25th built-in.
  • openspec/changes/flow-decision-tables/: proposal, design, tasks and spec. Design D-1 argues the storage decision: the table travels inline in the node config, so flow versioning, pinning and x-openregister-flows import apply with no extra machinery; a live object reference is refused there with reasons.

Hit policies

All five the shared evaluator implements: UNIQUE, FIRST, PRIORITY, ANY, COLLECT. That is a superset of what dossiq's tables use. Anything else is refused at save, by name.

No-match behaviour

A complete defaultOutputs row decides the default and flags defaulted: true in the evaluation record; without one the step fails loudly with no_rule_matched and the step's onError policy decides. A partial default row and a default row on COLLECT are refused at save.

Tests

78 decision-table assertions green, including a fixture translated from dossiq's real LHS enforcement matrix driven through dossiq's own inputMapping/outputMapping vocabulary, per-policy evaluation, no-match both ways, determinism, and every validation refusal. Full local Unit slice run; the only new failures were the listener test arity, fixed here.

Checks

  • phpcs, phpstan, psalm: clean on the changed files
  • phpmd (per subdirectory, foreground): clean
  • hydra gates --scope-to-diff: all diff-scoped gates pass. Gates 22 and 53 fail on development HEAD as well: hydra-gates v1.10.0's vendored manifest schema does not know the flow page type that nc-vue 2.27.0 defines and src/manifest.json page 18 uses. That fix belongs in ConductionNL/.github (vendored schema bump), not here.

For the dossiq migration

Replace an evaluateDecision transition action with an openregister.decision-table step: put the table definition under table, carry the existing inputMapping/outputMapping over unchanged, and add defaultOutputs only where a no-match should not fail the step. Nothing app-side needs to be declared or registered.

Conduction Release Bot added 2 commits September 2, 2026 10:52
openregister.decision-table evaluates an inline DMN-style table against
each item through the shared evaluator and writes the outputs onto
configured item fields. The table travels in the node config, so flow
versioning, pinning and x-openregister-flows import apply to the rules
with no extra machinery.

A table the evaluator cannot execute is refused when the flow is saved:
DecisionTableValidator probes every rule cell through the evaluator's
own grammar, so the accepted grammar is the executable one by
construction. No-match is an explicit choice, a complete default row or
a loud failure. The node never suspends: rule steps are the engine's,
human decisions stay with the user task.

The unit fixture is dossiq's real LHS enforcement matrix driven through
dossiq's own inputMapping/outputMapping vocabulary, proving the
migration that retires the app-side copy cannot strand.
…nd settle the analyzers

The listener test constructs every built-in by name, so the new node is a
25th argument there; the two StaticAccess findings are suppressed with
their reasons, and the long form-help strings are wrapped.
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openregister @ 49c8892

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-specs
test-l10n
test-l10n-parity
format
check-schema-l10n
check-l10n-js
composer ✅ 174/174
npm ✅ 543/543
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright ⏭️ deferred — runs on the promotion into beta/main, not on a pull request into development
Hydra gates

Quality workflow — 2026-09-02 09:21 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde
rubenvdlinde merged commit d1594cc into development Sep 2, 2026
47 checks passed
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