Skip to content

The decision-table editor

Decision Models is not yet available in a released build — see Decision models (DMN) for what that means. This page documents the editor as implemented; none of it is reachable through a shipped image today.

A DMN decision opens on a native Svelte editor at /dmn/add or /dmn/edit/<path> — there’s no separate widget to choose, and no dmn-js embed involved.

The bar above the editor carries the decision’s path (workspace-relative, e.g. f/demo/loan_approval), a free-text summary, and two actions: Save draft and Deploy.

Save draft never touches the live decision. updateDmnDecision — the endpoint that writes the live row — also deploys: for DMN that immediately changes what every in-flight POST .../evaluate call and every running businessRuleTask sees. So a draft always goes through the separate draft table instead (the same one Flows and Scripts use), and only Deploy calls createDmnDecision/updateDmnDecision. A brand-new, never-deployed decision that you save as a draft first gets a draft_only row created so it has somewhere to live.

Two tabs sit above the grid: Visual table and XML source. The XML tab is always available and is the source of truth when you edit it directly. The Visual tab re-parses the current XML every time you switch to it, and pins you to the XML tab with an inline reason (an Alert, not a silent fallback) when the document doesn’t fit the table model:

  • The document has no <decision> element, or more than one — the table editor represents exactly one.
  • The document defines any other DRG element (<inputData>, <decisionService>, and so on) alongside the decision.
  • The decision has requirement edges (<informationRequirement>, <knowledgeRequirement>, <authorityRequirement>) — i.e. it’s part of a DRD, which the table editor can’t represent.
  • The decision carries <extensionElements>.
  • The decision has no decision logic, or its logic isn’t a <decisionTable> (a literal-expression decision, for example).
  • The table’s hitPolicy isn’t one of the five Orvanta evaluates, or its aggregation isn’t SUM/MIN/MAX/COUNT.
  • Any rule’s entry count doesn’t match the table’s declared column count (malformed DMN).

Leaving the Visual tab flushes the in-memory table model back to XML first, so nothing you typed there is lost by switching tabs. Loading a document that does fit the table model never happens through a lossy round-trip either way — the same dmn-moddle parse/serialize pair backs both tabs.

The grid is built from CSS grid <div>s (not a <table>, so a future drag-and-drop layer isn’t fighting <thead>/<tbody> hoisting). Today, reordering is by button rather than drag handle:

  • Rules: add, duplicate, delete, and move up/down. Deleting a rule removes every FEEL entry it carries.
  • Input columns: add, delete, and move left/right. Adding one appends a blank entry to every existing rule; deleting one removes that column’s entry from every rule.
  • Output columns: same add/delete/move-left/right, mirrored onto every rule’s output entries.

Deleting a rule or a column is destructive and unrecoverable (no undo), so both go through a confirmation dialog naming exactly what’s lost, rather than a bare confirm().

Every column header carries a typeRef picker. The picker only offers Any, string, number, boolean, and date — the four scalar types the visual editor round-trips. The engine itself also evaluates time, date and time, and the two duration types (dayTimeDuration/yearMonthDuration — see Decision models), but a column typed with one of those isn’t distinguishable from Any once read into the Visual tab, and re-saving an untouched column with one of those types from the Visual tab silently drops it back to no typeRef at all. If you need one of the three temporal typeRefs, author or edit that column from the XML tab.

Every output column shows an output values field, regardless of hit policy — declaring one is an enforced domain constraint under every hit policy since #734, not decoration reserved for PRIORITY. The field’s placeholder changes when the table’s hit policy is PRIORITY, since that’s the one policy that also requires a declared list to rank by: “Output values, highest priority first” vs. the general “Allowed output values”.

The hit-policy select lists exactly the five policies the engine evaluates (Unique / First / Priority / Any / Collect), each with a one-line hint shown below the select — DMN’s RULE ORDER/OUTPUT ORDER policies are deliberately absent, since authoring one would immediately bail the document to xml-only on the next open. Selecting Collect reveals a second select for the aggregator (list / sum / min / max / count).

If the hit policy is Priority and no output column has declared output values yet, a warning banner says so before you try to evaluate: “PRIORITY needs output values — orvanta-dmn rejects a PRIORITY evaluation otherwise.”

The right-hand Test evaluate panel is always present alongside the grid. It derives its input fields from the decision’s declared input columns and always evaluates against the current in-editor XML, not the saved/deployed version — so it tests your unsaved edits, including ones made seconds ago.

Each field is labelled with the column’s display label but keyed internally by the column’s expression (the variable name the engine actually looks up), not the label — the two used to be conflated, which made any table with a human-readable label evaluate against a variable that was never supplied. Values you type are parsed as JSON when possible (so 42, true, "NL", [1,2] all work) and passed as-is otherwise.

Running a test shows:

  • Hit policy and matched rules (1-based rule numbers, highlighted directly on the grid).
  • Outputs, pretty-printed.
  • An unmapped inputs warning, if any of your input values had no FEEL representation and were silently omitted from the context the table evaluated against — the same warning orvanta dmn eval prints, since both paths share the identical input-mapping code.