dmn

package
v0.0.0-...-856ba3d Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 11, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Overview

Package dmn is the single public entry point of the Temis DMN engine.

It exposes the two-phase API described in docs/40-api-contract.md: an Engine compiles DMN models once into immutable, thread-safe CompiledDecisions, which are then evaluated cheaply and repeatedly against an input context.

Everything under internal/ is private and may change freely; the service/ and cmd/ packages access the engine exclusively through this package.

Typical use:

eng := dmn.New()
defs, diags, err := eng.Compile(ctx, xmlBytes)
if err != nil || diags.HasErrors() { /* handle */ }
dec, err := defs.Decision("Dish")
res, err := dec.Evaluate(ctx, dmn.Input{"Season": "Winter", "Guest Count": 8})
fmt.Println(res.Outputs["Dish"])

Evaluating a decision automatically evaluates the decisions it requires and feeds their results in by name, so the caller supplies only the leaf input data; a required result passed in directly is used as given. Result.Decisions reports every decision evaluated.

A Definitions.Service returns a compiled decision service, whose Evaluate runs its output decisions (and any encapsulated decisions) while treating its input decisions as caller-supplied boundaries.

Stability

The exported surface of this package is the SemVer-stable v1 contract (ADR-0011, ADR-0019). Additive changes ship in a minor release; a breaking change requires a major version. A symbol scheduled for removal is first marked // Deprecated: and dropped no earlier than the next major. Everything under internal/ is exempt and may change at any time. See docs/40-api-contract.md §4; the surface is frozen by a golden test (apisurface_test.go).

Example

Example shows the two-phase library flow: an Engine compiles a model once into reusable, thread-safe Definitions, from which a decision is fetched by name and evaluated against an input context.

package main

import (
	"context"
	"fmt"

	"github.com/pblumer/temis/dmn"
)

// A minimal DMN 1.5 model: one input (a number) and one decision whose literal
// FEEL expression doubles it. Real models are authored in a DMN editor and
// loaded as standard DMN XML; this one is inlined to keep the example
// self-contained.
const doubleModel = `<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="https://www.omg.org/spec/DMN/20230324/MODEL/"
             namespace="http://temis.example/double" name="Double" id="def_double">
  <inputData id="id_n" name="N">
    <variable name="N" typeRef="number"/>
  </inputData>
  <decision id="id_double" name="Double">
    <variable name="Double" typeRef="number"/>
    <informationRequirement>
      <requiredInput href="#id_n"/>
    </informationRequirement>
    <literalExpression><text>N * 2</text></literalExpression>
  </decision>
</definitions>`

func main() {
	eng := dmn.New()

	defs, diags, err := eng.Compile(context.Background(), []byte(doubleModel))
	if err != nil {
		panic(err) // malformed XML — a hard error
	}
	if diags.HasErrors() {
		panic(diags) // per-decision compile problems
	}

	dec, err := defs.Decision("Double")
	if err != nil {
		panic(err)
	}

	res, err := dec.Evaluate(context.Background(), dmn.Input{"N": 21})
	if err != nil {
		panic(err)
	}
	fmt.Println(res.Outputs["Double"])
}
Output:
42

Index

Examples

Constants

View Source
const (
	// CodeXMLMalformed marks a document that could not be decoded at all. It is
	// always returned as an error from Compile, never as a diagnostic.
	CodeXMLMalformed = "XML_MALFORMED"

	// CodeUnknownNamespace marks a document whose DMN namespace is not
	// recognised; it is decoded leniently. Severity: warning.
	CodeUnknownNamespace = "UNKNOWN_NAMESPACE"

	// CodeUnknownElement marks an XML element the mapper did not understand and
	// ignored. Severity: warning.
	CodeUnknownElement = "UNKNOWN_ELEMENT"

	// CodeNoLogic marks a decision that carries neither a literal expression nor
	// a decision table, so it has no executable logic. Severity: warning.
	CodeNoLogic = "DECISION_NO_LOGIC"

	// CodeFEELCompile marks a decision whose logic failed to compile. It is
	// reported as an error-severity diagnostic from Compile; the decision is
	// present in the model but not executable.
	CodeFEELCompile = "FEEL_COMPILE_ERROR"

	// CodeNotExecutable marks an Evaluate call on a decision that did not compile
	// to executable logic. Returned as an error from Evaluate.
	CodeNotExecutable = "DECISION_NOT_EXECUTABLE"

	// CodeMissingInput marks an Evaluate call missing a required input data value
	// the model references. Returned as an error from Evaluate.
	CodeMissingInput = "MISSING_REQUIRED_INPUT"

	// CodeLimitExceeded marks a resource limit being exhausted during evaluation
	// (ADR-0008). Returned as an error from Evaluate.
	CodeLimitExceeded = "LIMIT_EXCEEDED"

	// CodeUniqueMultiple marks a UNIQUE hit-policy decision table matching more
	// than one rule. Returned as an error from Evaluate.
	CodeUniqueMultiple = "UNIQUE_MULTIPLE_MATCH"

	// CodeDecisionCycle marks a dependency cycle in the decision graph, detected
	// at compile time. Reported as an error-severity diagnostic from Compile; the
	// decisions in the cycle cannot be evaluated. Severity: error.
	CodeDecisionCycle = "DECISION_CYCLE"

	// CodeServiceOutputUnresolved marks a decision service whose output decision
	// reference does not resolve to an executable decision. Reported as an
	// error-severity diagnostic from Compile. Severity: error.
	CodeServiceOutputUnresolved = "SERVICE_OUTPUT_UNRESOLVED"

	// CodeTypeError marks a statically provable type mismatch found by the
	// type-check phase (e.g. arithmetic on a string), carrying the source
	// position. It is advisory: evaluation still follows FEEL's null semantics, so
	// it is reported as a warning, not an error. Severity: warning.
	CodeTypeError = "TYPE_ERROR"

	// CodeRuntime is an honest placeholder for runtime failures that are not yet
	// exposed as a typed cause and so cannot be reliably classified at the API
	// edge (e.g. a resource-limit breach is today a bare error indistinguishable
	// from other runtime failures). It is additive: narrowing a specific runtime
	// failure to a more precise code later is a behavioural refinement, not a
	// removal of this code.
	CodeRuntime = "RUNTIME_ERROR"
)

Diagnostic and EvalError codes. Each constant names a stable error *class* — never a severity. The same class can surface at different severities (a missing-logic decision is a warning, a non-executable decision reached by Evaluate is an error); the code stays the same so callers can program against it regardless of how it is reported.

Stability: these string values are part of the public SemVer surface (docs/40-api-contract.md §1.4). They may only be extended additively. Renaming or removing a code, or repurposing its value, is a breaking change. Callers may program against Code; they must not rely on Message, which is human-readable and not stable.

Variables

This section is empty.

Functions

func ApplyEdits

func ApplyEdits(src []byte, edits []NodeEdit) ([]byte, error)

ApplyEdits applies position, name and type edits to a DMN XML document and returns the updated XML. It patches the existing document in place rather than regenerating it, so all decision logic (decision tables, FEEL, boxed expressions) and the rest of the DMNDI diagram are preserved untouched — only the named, retyped or repositioned elements change.

For each edit, a non-nil Name sets the element's display name attribute (inputData, decision or businessKnowledgeModel); a non-nil VarName sets a decision's or inputData's FEEL identifier (its <variable> name), written only when it differs from the display name; a non-nil DataType sets an inputData's variable typeRef; non-nil X and Y reposition the element's DMNShape in the diagram interchange (a no-op when the model carries no DMNDI). Edits for unknown ids are ignored. Renaming an element does not rewrite references to it elsewhere — keeping a downstream FEEL reference valid is the author's concern.

func ApplyGraph

func ApplyGraph(src []byte, edit GraphEdit) ([]byte, error)

ApplyGraph reconciles a DMN document to the desired graph and returns the updated XML. It patches in place, preserving every surviving decision's logic (decision tables, FEEL) and the rest of the document. New decisions are created without logic (undecided), to be filled in via the decision-table editor. Requirement edges and DMNDI shapes are reconciled: removed shapes/edges are dropped and new shapes use the supplied bounds (a model without DMNDI keeps no shapes, so the client auto-lays-out). Edges to or from an unknown node are ignored.

func ApplyTableEdit

func ApplyTableEdit(src []byte, decisionID string, edit TableEdit) ([]byte, error)

ApplyTableEdit rewrites the rule rows of a decision's decision table in a DMN document and returns the updated XML. It patches the existing document, so the table's columns, hit policy, the DMNDI and every other decision are preserved. An empty input entry is stored as "-" (the DMN "any" match). It errors when the decision has no decision-table logic.

func CreateBoxedConditional

func CreateBoxedConditional(src []byte, decisionID string) ([]byte, error)

CreateBoxedConditional gives an undecided decision a fresh boxed conditional (placeholder branches, ready to edit) and returns the updated XML. It errors when the decision is unknown or already has logic.

func CreateBoxedContext

func CreateBoxedContext(src []byte, decisionID string) ([]byte, error)

CreateBoxedContext gives an undecided decision a fresh boxed context (a single named entry, ready to edit) and returns the updated XML. It errors when the decision is unknown or already has logic.

func CreateBoxedFilter

func CreateBoxedFilter(src []byte, decisionID string) ([]byte, error)

CreateBoxedFilter gives an undecided decision a fresh boxed filter (placeholder branches, ready to edit) and returns the updated XML. It errors when the decision is unknown or already has logic.

func CreateBoxedInvocation

func CreateBoxedInvocation(src []byte, decisionID string) ([]byte, error)

CreateBoxedInvocation gives an undecided decision a fresh boxed invocation (placeholder called function and one binding) and returns the updated XML. It errors when the decision is unknown or already has logic.

func CreateBoxedIterator

func CreateBoxedIterator(src []byte, decisionID string) ([]byte, error)

CreateBoxedIterator gives an undecided decision a fresh boxed iteration (a placeholder `for`) and returns the updated XML. It errors when the decision is unknown or already has logic.

func CreateBoxedList

func CreateBoxedList(src []byte, decisionID string) ([]byte, error)

CreateBoxedList gives an undecided decision a fresh boxed list (a single placeholder item, ready to edit) and returns the updated XML. It errors when the decision is unknown or already has logic.

func CreateBoxedRelation

func CreateBoxedRelation(src []byte, decisionID string) ([]byte, error)

CreateBoxedRelation gives an undecided decision a fresh boxed relation (one column, one placeholder cell) and returns the updated XML. It errors when the decision is unknown or already has logic.

func CreateDecisionTable

func CreateDecisionTable(src []byte, decisionID string) ([]byte, error)

CreateDecisionTable gives a logic-less decision a fresh decision table and returns the updated XML. The table's input columns are derived from the decision's information requirements (so a decision wired into the DRG gets its inputs for free), with a single output named after the decision; it starts with no rules, to be filled via the table editor. It errors when the decision is unknown or already has logic.

func RemoveItemDefinition

func RemoveItemDefinition(src []byte, name string) ([]byte, error)

RemoveItemDefinition removes the named item definition and returns the updated XML. References to the type elsewhere are left as-is (the author's concern). It errors when no such type exists.

func SetBKMFunction

func SetBKMFunction(src []byte, bkmID string, edit BKMFunctionEdit) ([]byte, error)

SetBKMFunction sets a business knowledge model's encapsulated logic to a function with the given parameters and literal body, returning the updated XML. An empty body is rejected; so is a BKM whose current body is a non-literal boxed expression (which this editor must not overwrite). Parameters with an empty name are dropped.

func SetBoxedConditional

func SetBoxedConditional(src []byte, decisionID string, edit ConditionalEdit) ([]byte, error)

SetBoxedConditional sets (or replaces) a decision's boxed-conditional logic from edit and returns the updated XML. Each branch must be a non-empty FEEL expression. It errors when the decision is unknown or already carries non-conditional logic (use the matching editor for that).

func SetBoxedContext

func SetBoxedContext(src []byte, decisionID string, edit ContextEdit) ([]byte, error)

SetBoxedContext sets (or replaces) a decision's boxed-context logic from edit and returns the updated XML. Each named entry must have a non-empty name and expression; the optional result cell is the context's value (otherwise the value is a context keyed by the entry names). It errors when the decision is unknown or already carries non-context logic (use the matching editor for that).

func SetBoxedFilter

func SetBoxedFilter(src []byte, decisionID string, edit FilterEdit) ([]byte, error)

SetBoxedFilter sets (or replaces) a decision's boxed-filter logic from edit and returns the updated XML. Both branches must be non-empty FEEL expressions. It errors when the decision is unknown or already carries non-filter logic (use the matching editor for that).

func SetBoxedInvocation

func SetBoxedInvocation(src []byte, decisionID string, edit InvocationEdit) ([]byte, error)

SetBoxedInvocation sets (or replaces) a decision's boxed-invocation logic from edit and returns the updated XML. The called function must be named; bindings with a blank parameter and value are dropped, and every remaining binding needs a unique parameter name and a non-empty argument. It errors when the decision is unknown or already carries non-invocation logic (use the matching editor).

func SetBoxedIterator

func SetBoxedIterator(src []byte, decisionID string, edit IteratorEdit) ([]byte, error)

SetBoxedIterator sets (or replaces) a decision's boxed-iteration logic from edit and returns the updated XML. kind must be "for", "some" or "every"; the variable, collection and body must all be non-empty. It errors when the decision is unknown or already carries non-iteration logic (use the matching editor for that).

func SetBoxedList

func SetBoxedList(src []byte, decisionID string, edit ListEdit) ([]byte, error)

SetBoxedList sets (or replaces) a decision's boxed-list logic from edit and returns the updated XML. Blank items are dropped; the list must end up with at least one item. It errors when the decision is unknown or already carries non-list logic (use the matching editor for that).

func SetBoxedRelation

func SetBoxedRelation(src []byte, decisionID string, edit RelationEdit) ([]byte, error)

SetBoxedRelation sets (or replaces) a decision's boxed-relation logic from edit and returns the updated XML. Columns must be non-empty and uniquely named; fully blank rows are dropped, and every remaining row must have exactly one non-empty FEEL cell per column. It errors when the decision is unknown or already carries non-relation logic (use the matching editor for that).

func SetItemDefinition

func SetItemDefinition(src []byte, t ItemType) ([]byte, error)

SetItemDefinition creates or updates an item definition and returns the updated XML. With Components it upserts a STRUCTURED type (its fields, one level: name + type + collection); nest by referencing another named type. Without Components it upserts a SIMPLE type (base type + collection + allowed values) and errors if the existing definition of that name is structured. An empty name (or a struct field with an empty name) is an error.

func SetLiteralExpression

func SetLiteralExpression(src []byte, decisionID, text, typeRef string) ([]byte, error)

SetLiteralExpression sets (or creates) a decision's literal-expression logic and returns the updated XML. The text is stored verbatim; an empty text is rejected (a literal decision must have an expression). It errors when the decision is unknown or already carries non-literal logic (e.g. a decision table — use the table editor for that).

func SetLogic

func SetLogic(src []byte, a Anchor, at, kind string, raw json.RawMessage) ([]byte, error)

SetLogic writes an edited boxed expression back to the anchored element and returns the recompiled XML. For a decision anchor it delegates to the existing per-kind setters (unchanged behaviour); for a BKM anchor it rewrites the encapsulated-logic body, preserving the function's formal parameters. raw is the kind's typed edit payload as JSON.

func SetModelName

func SetModelName(src []byte, name string) ([]byte, error)

SetModelName sets the DMN definitions' name (the model's editable display name) and returns the updated XML. Like ApplyEdits it patches the existing document in place, so all decisions, logic and the DMNDI diagram are preserved untouched — only the top-level name attribute changes. It is the model-rename counterpart to ApplyEdits' per-element renames (ADR-0016).

Types

type Anchor

type Anchor struct {
	Kind string `json:"kind"`
	ID   string `json:"id"`
}

Anchor names the model element whose boxed-expression logic an editor targets: a decision's own logic (Kind "decision") or a business knowledge model's encapsulated-logic body (Kind "bkm"). ID is the element's id or name. It is the generalisation of the decision-only routes (ADR-0016, WP-66) that lets the same per-kind editors edit a BKM's boxed body — the read-only wall in the simple BKM editor.

type BKMFunctionEdit

type BKMFunctionEdit struct {
	Params      []BKMParam `json:"params"`
	BodyText    string     `json:"bodyText"`
	BodyTypeRef string     `json:"bodyTypeRef"`
}

BKMFunctionEdit is the editable payload for a BKM's function: its formal parameters and a literal FEEL body.

type BKMParam

type BKMParam struct {
	Name    string `json:"name"`
	TypeRef string `json:"typeRef,omitempty"`
}

BKMParam is one formal parameter of a business knowledge model's function.

type BKMView

type BKMView struct {
	BkmID       string     `json:"bkmId"`
	Name        string     `json:"name"`
	Params      []BKMParam `json:"params"`
	BodyText    string     `json:"bodyText"`
	BodyTypeRef string     `json:"bodyTypeRef,omitempty"`
	Simple      bool       `json:"simple"`
	// BodyKind names the boxed kind of a non-simple body (table, context, list,
	// relation, invocation, iterator, conditional, filter, function), so the
	// modeler can open the matching boxed editor on it (WP-66). It is empty for a
	// simple (literal or empty) body.
	BodyKind string `json:"bodyKind,omitempty"`
}

BKMView is a business knowledge model's encapsulated logic for the modeler: its formal parameters and a literal FEEL body. Simple is false when the body is a non-literal boxed expression (a table/context/…), which the simple editor shows read-only.

type CompiledDecision

type CompiledDecision struct {
	// contains filtered or unexported fields
}

CompiledDecision is a single decision's compiled logic. It is immutable and thread-safe, so one instance may be evaluated concurrently any number of times against different inputs.

func (*CompiledDecision) Evaluate

func (c *CompiledDecision) Evaluate(ctx context.Context, in Input, opts ...EvalOption) (Result, error)

Evaluate runs the decision against in and returns its result. Compilation has already happened, so this is the cheap, repeatable phase.

Go inputs convert to FEEL values as follows: nil→null, bool→boolean, the integer and floating-point kinds→number (decimal; float inputs may lose precision — prefer string or integer for exact amounts), string→string, time.Time→date and time, []any→list, map[string]any→context. A value already of the engine's internal value type is passed through.

FEEL results convert back to Go with numbers rendered as their exact decimal string (ADR-0007), booleans as bool, strings as string, temporal values and ranges as their canonical FEEL string, lists as []any and contexts as map[string]any.

Evaluate is hard (fail-fast): it returns a non-nil error in exactly these cases. Most are an *EvalError, classifiable via its Code; a strict input-validation failure (WithStrictInput) is an *InputError instead.

  • *InputError: with WithStrictInput, the input does not satisfy the decision's declared schema (wrong type, unknown or missing input). Checked first, so it supersedes the CodeMissingInput case below.
  • CodeNotExecutable: the decision did not compile to executable logic. The caller is expected to check diags.HasErrors() after Compile; reaching here is a caller bug, not a data case, and is not masked as a null.
  • CodeMissingInput: a required input data value the model references is absent from in. Also a caller bug; not masked as a null.
  • CodeUniqueMultiple: a UNIQUE hit-policy table matched more than one rule (classified from a typed cause in internal/boxed).
  • CodeRuntime: the context was cancelled or its deadline passed, or the expression failed at runtime in a way not yet exposed as a typed cause. CodeLimitExceeded is reserved for the resource-limit path (ADR-0008): once limits are wired and their breach is typed, those failures move from CodeRuntime to CodeLimitExceeded.

A spec-conformant FEEL null (a runtime type mismatch, division by zero, …) is NOT an error: it becomes a nil output in Result, optionally with a warning/info diagnostic in Result.Diags.

Optional behaviour is opt-in via EvalOption (WithTrace, WithStrictInput); without options the call is lenient and allocation-lean as before.

Example

ExampleCompiledDecision_Evaluate reuses one compiled decision across several inputs. Compilation happens once; each Evaluate is cheap and concurrency-safe.

package main

import (
	"context"
	"fmt"

	"github.com/pblumer/temis/dmn"
)

// A minimal DMN 1.5 model: one input (a number) and one decision whose literal
// FEEL expression doubles it. Real models are authored in a DMN editor and
// loaded as standard DMN XML; this one is inlined to keep the example
// self-contained.
const doubleModel = `<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="https://www.omg.org/spec/DMN/20230324/MODEL/"
             namespace="http://temis.example/double" name="Double" id="def_double">
  <inputData id="id_n" name="N">
    <variable name="N" typeRef="number"/>
  </inputData>
  <decision id="id_double" name="Double">
    <variable name="Double" typeRef="number"/>
    <informationRequirement>
      <requiredInput href="#id_n"/>
    </informationRequirement>
    <literalExpression><text>N * 2</text></literalExpression>
  </decision>
</definitions>`

func main() {
	defs, _, err := dmn.New().Compile(context.Background(), []byte(doubleModel))
	if err != nil {
		panic(err)
	}
	dec, err := defs.Decision("Double")
	if err != nil {
		panic(err)
	}

	for _, n := range []int{1, 10, 100} {
		res, err := dec.Evaluate(context.Background(), dmn.Input{"N": n})
		if err != nil {
			panic(err)
		}
		fmt.Printf("%d -> %v\n", n, res.Outputs["Double"])
	}
}
Output:
1 -> 2
10 -> 20
100 -> 200

func (*CompiledDecision) ID

func (c *CompiledDecision) ID() string

ID returns the decision's identifier.

func (*CompiledDecision) InputSchema

func (c *CompiledDecision) InputSchema() []InputField

InputSchema returns the inputs the decision expects, with their declared types.

func (*CompiledDecision) Name

func (c *CompiledDecision) Name() string

Name returns the decision's name.

func (*CompiledDecision) ValidateInput

func (c *CompiledDecision) ValidateInput(in Input) []InputProblem

ValidateInput checks in against the decision's declared schema and returns every problem found (an empty slice means the input is valid). It reports inputs of the wrong type, inputs the decision does not declare, and missing required inputs — turning what would otherwise be a silently wrong result into an explicit, actionable list. It never evaluates the decision.

type CompiledExpression

type CompiledExpression struct {
	// contains filtered or unexported fields
}

CompiledExpression is a standalone compiled FEEL expression: the engine's FEEL evaluator exposed on its own, without a surrounding decision. It is immutable and safe to evaluate concurrently. Compile once with CompileExpression, then Evaluate repeatedly against different inputs.

It exists so higher layers can use FEEL as a small expression language over a named context — notably the decision-flow layer (package flow, ADR-0026), whose step-input mappings are FEEL expressions over the flow's inputs and earlier steps' outputs.

func CompileExpression

func CompileExpression(expr string, names ...string) (*CompiledExpression, error)

CompileExpression parses and compiles a FEEL expression that may reference the given variable names. A name the expression uses that is not in names is a compile error ("unknown variable"), so the caller declares the context up front — exactly the names it will supply to Evaluate.

The expression is compiled with the engine's default configuration: the full FEEL built-in library and standard value semantics (ADR-0003/0007). now() and today() read the process clock and are therefore not deterministic here; pass a fixed value as an input instead when determinism matters.

func (*CompiledExpression) Evaluate

func (c *CompiledExpression) Evaluate(ctx context.Context, in Input) (any, error)

Evaluate runs the expression against in (variable name → Go value, converted to FEEL values as documented on CompiledDecision.Evaluate) and returns the result converted back to Go. A name declared at compile time but absent from in evaluates to null. A spec-conformant FEEL null (type mismatch, division by zero, …) is returned as a nil value, not an error.

func (*CompiledExpression) References

func (c *CompiledExpression) References() []string

References returns the subset of the declared names the expression actually uses, sorted. It lets a caller learn an expression's dependencies (e.g. to order a graph of expressions) without re-parsing it.

type CompiledService

type CompiledService struct {
	// contains filtered or unexported fields
}

CompiledService is a compiled DMN decision service: a reusable unit that evaluates its output (and any encapsulated) decisions and returns the output decisions' results. It is immutable and safe to evaluate concurrently.

func (*CompiledService) Evaluate

func (s *CompiledService) Evaluate(ctx context.Context, in Input) (Result, error)

Evaluate runs the decision service against in and returns its result. Output decisions (and the encapsulated decisions they require) are evaluated; the service's input decisions are treated as caller-supplied boundaries and are not computed. Result.Outputs is keyed by output-decision name; Result.Decisions holds every decision the service actually evaluated.

func (*CompiledService) ID

func (s *CompiledService) ID() string

ID returns the service's identifier.

func (*CompiledService) Name

func (s *CompiledService) Name() string

Name returns the service's name.

type ConditionalEdit

type ConditionalEdit struct {
	If   string `json:"if"`
	Then string `json:"then"`
	Else string `json:"else"`
}

ConditionalEdit is the editable payload for a boxed conditional: the three FEEL branches. All three are required.

type ConditionalView

type ConditionalView struct {
	DecisionID string `json:"decisionId"`
	Name       string `json:"name"`
	If         string `json:"if"`
	Then       string `json:"then"`
	Else       string `json:"else"`
	Simple     bool   `json:"simple"`
}

ConditionalView is a decision's boxed-conditional logic for the modeler: the three FEEL branches of an if/then/else. Simple is false when any branch is itself a nested boxed expression (not a literal), which this text view cannot represent — the editor then opens read-only so it never clobbers the nesting.

type ContextEdit

type ContextEdit struct {
	Entries       []ContextEntryView `json:"entries"`
	Result        string             `json:"result,omitempty"`
	ResultTypeRef string             `json:"resultTypeRef,omitempty"`
}

ContextEdit is the editable payload for a boxed context: named entries (each a literal FEEL expression) and an optional result-cell expression. It replaces the decision's context entries wholesale.

type ContextEntryView

type ContextEntryView struct {
	Name      string `json:"name"`
	Text      string `json:"text"`
	TypeRef   string `json:"typeRef,omitempty"`
	Index     int    `json:"index"`
	ChildKind string `json:"childKind,omitempty"`
}

ContextEntryView is one boxed-context entry: a bound name and its literal FEEL expression with an optional declared result type. Index is the entry's position in the context (used as the `entry.N` locator step for a drill-in). ChildKind is set when the entry's value is itself a nested boxed expression rather than a literal, naming which boxed editor edits it in place (WP-66 Phase 2); Text is then empty.

type ContextView

type ContextView struct {
	DecisionID    string             `json:"decisionId"`
	Name          string             `json:"name"`
	Entries       []ContextEntryView `json:"entries"`
	Result        string             `json:"result,omitempty"`
	ResultTypeRef string             `json:"resultTypeRef,omitempty"`
	Simple        bool               `json:"simple"`
}

ContextView is a decision's boxed-context logic for the modeler: an ordered list of named entries (each a literal FEEL expression) and an optional result-cell expression. Simple is false when any entry's value is itself a nested boxed expression (not a literal), which this text-based view cannot represent — the editor then opens read-only so it never clobbers the nesting.

type Definitions

type Definitions struct {
	// contains filtered or unexported fields
}

Definitions is a compiled DMN model: the set of decisions a document declares, each ready to evaluate. It is immutable after Compile and safe to share.

func (*Definitions) BKMFunction

func (d *Definitions) BKMFunction(idOrName string) (BKMView, bool)

BKMFunction returns a business knowledge model's encapsulated-logic view. ok is false when no such BKM exists.

func (*Definitions) BoxedConditional

func (d *Definitions) BoxedConditional(idOrName string) (ConditionalView, bool)

BoxedConditional returns the decision's boxed-conditional view. ok is false when no such decision exists or its logic is not a boxed conditional.

func (*Definitions) BoxedContext

func (d *Definitions) BoxedContext(idOrName string) (ContextView, bool)

BoxedContext returns the decision's boxed-context view. ok is false when no such decision exists or its logic is not a boxed context.

func (*Definitions) BoxedFilter

func (d *Definitions) BoxedFilter(idOrName string) (FilterView, bool)

BoxedFilter returns the decision's boxed-filter view. ok is false when no such decision exists or its logic is not a boxed filter.

func (*Definitions) BoxedInvocation

func (d *Definitions) BoxedInvocation(idOrName string) (InvocationView, bool)

BoxedInvocation returns the decision's boxed-invocation view. ok is false when no such decision exists or its logic is not a boxed invocation.

func (*Definitions) BoxedIterator

func (d *Definitions) BoxedIterator(idOrName string) (IteratorView, bool)

BoxedIterator returns the decision's boxed-iteration view. ok is false when no such decision exists or its logic is not a for/some/every iteration.

func (*Definitions) BoxedList

func (d *Definitions) BoxedList(idOrName string) (ListView, bool)

BoxedList returns the decision's boxed-list view. ok is false when no such decision exists or its logic is not a boxed list.

func (*Definitions) BoxedRelation

func (d *Definitions) BoxedRelation(idOrName string) (RelationView, bool)

BoxedRelation returns the decision's boxed-relation view. ok is false when no such decision exists or its logic is not a boxed relation.

func (*Definitions) Decision

func (d *Definitions) Decision(idOrName string) (*CompiledDecision, error)

Decision returns the compiled decision identified by idOrName. It is an error if no such decision exists, or if it exists but has no executable logic.

func (*Definitions) DecisionTable

func (d *Definitions) DecisionTable(idOrName string) (TableView, bool)

DecisionTable returns the decision's decision-table view. ok is false when no such decision exists or its logic is not a decision table (e.g. a literal expression or context), so the modeler can fall back gracefully.

func (*Definitions) EvaluateGraph

func (d *Definitions) EvaluateGraph(ctx context.Context, in Input, opts ...EvalOption) (GraphResult, error)

EvaluateGraph evaluates every executable decision in the model against in and returns each one's value (and trace when WithTrace is set), so a caller can present the whole decision requirements graph computed from a single set of leaf inputs — exactly what an "evaluate the graph" view needs (the user fills the input data once and sees every decision's result).

With WithStrictInput, in is validated once against the model's whole-graph input schema (ModelInputSchema) and a non-conforming input fails with an *InputError before any decision runs; an input reached only transitively (named by a downstream decision but not the one being shown) is accepted. Without it, evaluation is lenient.

Each decision is evaluated as its own root, so it carries its own trace and a failure in one decision (recorded in GraphResult.Errors) does not blank the others. Shared sub-decisions are recomputed; this is fine for the interactive use this serves. Strict validation, when requested, is applied once at the graph level, so the per-decision evaluations here run leniently.

func (*Definitions) Functions

func (d *Definitions) Functions() []FeelFunction

Functions lists every business knowledge model as an invocable FEEL function signature (name plus ordered parameter names). The modeler hands this to its FEEL editors so calls to a BKM — from a decision, from a sibling BKM, or a BKM's own recursion — complete and validate as known functions. It mirrors the engine's compileBKMs, which registers exactly these names before any body compiles (so recursion resolves), keeping the editor in step with what actually evaluates.

func (*Definitions) Graph

func (d *Definitions) Graph() Graph

Graph returns the model's decision requirements graph. Node ids are the local DMN element identifiers; edges reference them. Edges whose endpoint is not a known node (dangling references) are skipped.

func (*Definitions) Index

func (d *Definitions) Index() ModelIndex

Index returns the names of the model's decisions and input data. Only decisions with executable logic are listed.

func (*Definitions) InputSchema

func (d *Definitions) InputSchema(idOrName string) ([]InputField, error)

InputSchema returns the declared input schema of a decision by id or name.

func (*Definitions) ItemDefinitions

func (d *Definitions) ItemDefinitions() []ItemType

ItemDefinitions returns the model's named type definitions, for the modeler's type manager and the type pickers.

func (*Definitions) LiteralExpression

func (d *Definitions) LiteralExpression(idOrName string) (LiteralView, bool)

LiteralExpression returns the decision's literal-expression view. ok is false when no such decision exists or its logic is not a literal expression.

func (*Definitions) LogicView

func (d *Definitions) LogicView(a Anchor, at, kind string) (any, bool)

LogicView returns the anchored element's boxed logic as the typed view for the requested kind (the same view shapes the decision routes return), or ok=false when the anchor is unknown or its logic is not of that kind. It is how the modeler reads a BKM's boxed body into the matching kind's editor (WP-66).

func (*Definitions) ModelInputSchema

func (d *Definitions) ModelInputSchema() []InputField

ModelInputSchema returns the input data the whole model consumes — the union of every decision's declared input fields, deduped by name. It is the schema for a graph-wide evaluation (EvaluateGraph): the leaf inputs a caller fills once to drive every decision, including those reached only transitively through other decisions (e.g. an input a downstream decision never names directly). A field's type/constraint is taken from the first decision that declares it with one, and it is required when any decision requires it.

func (*Definitions) ModelName

func (d *Definitions) ModelName() string

ModelName returns the DMN definitions' name (the editable model name), or "" when the document declares none.

func (*Definitions) ReachableInputSchema

func (d *Definitions) ReachableInputSchema(idOrName string) ([]InputField, error)

ReachableInputSchema returns the leaf inputs needed to evaluate the decision idOrName: its directly declared inputs plus those reached transitively through required decisions — the union over its requirements cone, deduped by name exactly as ModelInputSchema but scoped to that one decision (ADR-0026, L2a). ModelInputSchema is the union over the whole model and would allow inputs of other, unrelated decisions; ReachableInputSchema is the minimal correct superset for driving one composed decision — e.g. in a flow, precisely the leaf inputs a step targeting the decision may wire. A field's type/constraint comes from the first declaring decision in the cone, and it is required when any decision in the cone requires it. It errs if no such decision exists.

func (*Definitions) Service

func (d *Definitions) Service(idOrName string) (*CompiledService, error)

Service returns the compiled decision service identified by idOrName.

func (*Definitions) ValidateModelInput

func (d *Definitions) ValidateModelInput(in Input) []InputProblem

ValidateModelInput checks in against the model's whole-graph input schema (ModelInputSchema) and returns every problem found — the model-level counterpart of CompiledDecision.ValidateInput, used by EvaluateGraph's strict mode so a transitively-reached input is accepted (it feeds some decision) while a genuinely unknown one is still reported.

func (*Definitions) ValidateReachableInput

func (d *Definitions) ValidateReachableInput(idOrName string, in Input) ([]InputProblem, error)

ValidateReachableInput checks in against the decision's reachable input schema (ReachableInputSchema) and returns every problem found — the cone-scoped counterpart of ValidateModelInput. It lets a caller (notably a flow step) validate an input that includes transitively-reached leaf inputs while still catching genuine unknowns, type mismatches and missing required inputs. It errs if no such decision exists.

type Diagnostic

type Diagnostic struct {
	Severity Severity
	// Code is the stable, machine-readable error class, one of the Code*
	// constants (e.g. CodeFEELCompile). It names the failure class, never the
	// severity, and is part of the SemVer surface — callers may program against
	// it. Message is human-readable and NOT stable; do not parse it.
	Code       string
	Message    string
	DecisionID string
	Line, Col  int // source position; 0 when not applicable
}

Diagnostic is a single problem found while compiling or evaluating a model. It is never a panic: user errors are reported, not fatal.

type Diagnostics

type Diagnostics []Diagnostic

Diagnostics is a collection of diagnostics returned by Compile or Evaluate.

func (Diagnostics) HasErrors

func (d Diagnostics) HasErrors() bool

HasErrors reports whether any diagnostic has error severity.

type Engine

type Engine struct {
	// contains filtered or unexported fields
}

Engine compiles DMN models. It is re-entrant and holds no mutable state, so a single Engine may be shared across goroutines.

func New

func New(opts ...Option) *Engine

New returns an Engine configured with the given options.

func (*Engine) Compile

func (e *Engine) Compile(ctx context.Context, xml []byte) (*Definitions, Diagnostics, error)

Compile decodes and compiles a complete DMN XML document. Malformed XML is a hard error; per-decision problems (unknown variables, unsupported constructs, unrecognised namespaces) are reported through the returned Diagnostics while the rest of the model still compiles. Decisions whose logic fails to compile are present in the result but not executable.

type EvalError

type EvalError struct {
	Code       string // one of the Code* constants
	DecisionID string // the decision being evaluated, when known
	Message    string // human-readable detail; not stable, do not parse
	Err        error  // wrapped cause, when one exists
}

EvalError is the typed error returned by Evaluate when an evaluation could not be carried out: the decision is not executable, a required input is missing, the context was cancelled, a resource limit was exhausted, or another runtime failure occurred. Code is one of the Code* constants and is the stable, machine-readable classification; callers should switch on Code, not parse Message.

A spec-conformant FEEL null is NOT an EvalError. It is an ordinary Result with a nil output and, optionally, a non-error (warning/info) diagnostic in Diags.

func (*EvalError) Error

func (e *EvalError) Error() string

Error renders the error as `dmn: <CODE>: decision "<id>": <message>`. The decision clause is omitted when DecisionID is empty.

func (*EvalError) Unwrap

func (e *EvalError) Unwrap() error

Unwrap returns the wrapped cause so errors.Is/As can traverse it.

type EvalOption

type EvalOption func(*evalConfig)

EvalOption tunes a single Evaluate call.

func WithStrictInput

func WithStrictInput() EvalOption

WithStrictInput makes Evaluate validate the input against the decision's declared schema first and fail with an *InputError if it does not conform — instead of silently coercing a wrong-typed or misnamed value into a null or a non-match (ADR-0013, WP-52). Without it, evaluation is lenient as before.

func WithTrace

func WithTrace() EvalOption

WithTrace makes Evaluate attach a structured explanation (which rules matched and why) to Result.Trace. It is opt-in: without it, evaluation takes the allocation-free path and Result.Trace stays nil (ADR-0013, WP-51).

type FeelFunction

type FeelFunction struct {
	Name   string   `json:"name"`
	Params []string `json:"params"`
}

FeelFunction is one user-defined invocable function of a model — a business knowledge model — exposed to the modeler so its FEEL editors know the model's callable functions. Params are the formal parameter names in order (for a signature hint). It lets the editor offer these functions in code completion and recognise calls to them during live validation, a BKM's own recursive call included (it is a function in its own model), rather than flagging the name as unknown.

type FilterEdit

type FilterEdit struct {
	In    string `json:"in"`
	Match string `json:"match"`
}

FilterEdit is the editable payload for a boxed filter: the two FEEL branches. Both are required.

type FilterView

type FilterView struct {
	DecisionID string `json:"decisionId"`
	Name       string `json:"name"`
	In         string `json:"in"`
	Match      string `json:"match"`
	Simple     bool   `json:"simple"`
}

FilterView is a decision's boxed-filter logic for the modeler: the collection (`in`) and the predicate (`match`), which is evaluated for each element with `item` bound to it. Simple is false when either branch is itself a nested boxed expression (not a literal), which this text view cannot represent — the editor then opens read-only so it never clobbers the nesting.

type Graph

type Graph struct {
	Nodes []GraphNode `json:"nodes"`
	Edges []GraphEdge `json:"edges"`
}

Graph is the decision requirements graph (DRG) of a model: its nodes and the requirement edges between them, for tooling that draws the diagram — notably the own modeler frontend (ADR-0016), which renders this directly rather than parsing DMN XML in the browser. It carries JSON tags as part of that wire contract.

type GraphEdge

type GraphEdge struct {
	Type   string `json:"type"`
	Source string `json:"source"`
	Target string `json:"target"`
}

GraphEdge is one requirement, directed from the required (upstream) element to the element that requires it — matching the DMN arrow direction. Type is "informationRequirement" (data/decision dependency) or "knowledgeRequirement" (BKM dependency).

type GraphEdgeEdit

type GraphEdgeEdit struct {
	Type   string `json:"type"`
	Source string `json:"source"`
	Target string `json:"target"`
}

GraphEdgeEdit is one desired requirement edge, directed from Source (the required element) to Target (the requiring element). Type is "informationRequirement" or "knowledgeRequirement".

type GraphEdit

type GraphEdit struct {
	Nodes []GraphNodeEdit `json:"nodes"`
	Edges []GraphEdgeEdit `json:"edges"`
}

GraphEdit is the desired decision requirements graph for a structural save: the complete set of nodes and requirement edges the model should have. ApplyGraph reconciles the existing document to it — creating added elements, removing absent ones and updating the rest — so the modeler can persist add/delete, not just attribute edits (ADR-0016). Because reconciliation is to the FULL set, the client must send every node and edge currently on the canvas, not a delta.

type GraphNode

type GraphNode struct {
	ID   string `json:"id"`
	Type string `json:"type"`
	Name string `json:"name"`
	// DataType is the node's resolved FEEL type (the InputData's type, or a
	// decision's output type), for showing the data contract. "" when unknown.
	DataType string `json:"dataType,omitempty"`
	// VarName is a decision's output-variable name (how its result is referenced
	// downstream); defaults to the decision name. Empty for non-decisions.
	VarName string `json:"varName,omitempty"`
	// HasTable marks a decision whose logic is a decision table, so the modeler can
	// offer to open it (double-click). False for non-decisions and for decisions
	// with other logic (literal expression, context, …).
	HasTable bool `json:"hasTable,omitempty"`
	// HasLiteral marks a decision whose logic is a literal FEEL expression, so the
	// modeler opens the expression editor on double-click.
	HasLiteral bool `json:"hasLiteral,omitempty"`
	// HasContext marks a decision whose logic is a boxed context, so the modeler
	// opens the boxed-context editor (WP-66) rather than treating it as an
	// uneditable boxed expression.
	HasContext bool `json:"hasContext,omitempty"`
	// HasConditional marks a decision whose logic is a boxed conditional
	// (if/then/else), so the modeler opens the conditional editor on double-click.
	HasConditional bool `json:"hasConditional,omitempty"`
	// HasList marks a decision whose logic is a boxed list, so the modeler opens the
	// list editor on double-click.
	HasList bool `json:"hasList,omitempty"`
	// HasRelation marks a decision whose logic is a boxed relation, so the modeler
	// opens the relation grid editor on double-click.
	HasRelation bool `json:"hasRelation,omitempty"`
	// HasFilter marks a decision whose logic is a boxed filter, so the modeler opens
	// the filter editor on double-click.
	HasFilter bool `json:"hasFilter,omitempty"`
	// HasIterator marks a decision whose logic is a boxed iteration (for/some/every),
	// so the modeler opens the iterator editor on double-click.
	HasIterator bool `json:"hasIterator,omitempty"`
	// HasInvocation marks a decision whose logic is a boxed invocation (a function/
	// BKM call), so the modeler opens the invocation editor on double-click.
	HasInvocation bool `json:"hasInvocation,omitempty"`
	// HasLogic marks a decision that has ANY executable logic (a table, a literal
	// or another boxed expression), so the modeler can show a table icon vs a
	// boxed-expression icon vs an "undecided" (no logic) icon.
	HasLogic bool    `json:"hasLogic,omitempty"`
	X        float64 `json:"x,omitempty"`
	Y        float64 `json:"y,omitempty"`
	Width    float64 `json:"width,omitempty"`
	Height   float64 `json:"height,omitempty"`
}

GraphNode is one DRG element. Type is one of "decision", "inputData" or "businessKnowledgeModel". X/Y/Width/Height carry the authored DMNDI bounds when the model has a diagram (omitted otherwise, so the client falls back to auto-layout).

type GraphNodeEdit

type GraphNodeEdit struct {
	ID   string `json:"id"`
	Type string `json:"type"`
	Name string `json:"name"`
	// VarName is the element's FEEL identifier (decision/inputData variable name),
	// distinct from the free-form display Name. Persisted as an explicit <variable>
	// only when it differs from Name; empty or equal lets it follow the name.
	VarName  string  `json:"varName,omitempty"`
	DataType string  `json:"dataType,omitempty"`
	X        float64 `json:"x"`
	Y        float64 `json:"y"`
	Width    float64 `json:"width"`
	Height   float64 `json:"height"`
}

GraphNodeEdit is one desired node. Type is "inputData", "decision" or "businessKnowledgeModel". DataType (inputData only) sets the declared FEEL type. X/Y/Width/Height are the node's DMNDI shape bounds.

type GraphResult

type GraphResult struct {
	// Values holds every executable decision's result, keyed by decision name.
	Values map[string]any
	// Traces holds each decision's structured explanation, present only with
	// WithTrace and only for decisions that evaluated without error.
	Traces map[string]*Trace
	// Errors holds the message for each decision that failed to evaluate, keyed by
	// name; nil when every decision succeeded.
	Errors map[string]string
	// Diags collects the runtime diagnostics gathered across all decisions.
	Diags Diagnostics
}

GraphResult is the outcome of evaluating a whole model (EvaluateGraph): each decision's value and, with WithTrace, its trace, keyed by decision name — so a caller can show the entire decision requirements graph computed from a single set of leaf inputs. A decision that fails to evaluate (e.g. a runtime error) has its message in Errors and no entry in Values; a spec-conformant null is a nil value, not an error.

type Input

type Input map[string]any

Input is an evaluation context: variable name → Go value. Keys are input-data or required-decision names; values are converted to FEEL values per the mapping documented on Evaluate. Names the model does not reference are ignored. A referenced required input data name that is absent is a caller error (Evaluate returns an EvalError with CodeMissingInput, not a silent null); other referenced names absent from the map evaluate to FEEL null.

type InputError

type InputError struct {
	Problems []InputProblem
}

InputError is returned by Evaluate under WithStrictInput when the supplied input does not satisfy the decision's declared schema. It carries every problem found, so a caller (notably an agent) gets the full picture in one go rather than one error at a time.

func (*InputError) Error

func (e *InputError) Error() string

type InputField

type InputField struct {
	Name     string `json:"name"`
	Type     string `json:"type,omitempty"`
	Required bool   `json:"required"`
	// Constraint is the input's allowed-values text (a FEEL unary-test list, e.g.
	// `"red","green","blue"` or `[1..10]`), empty when unconstrained. It lets an
	// agent see the permitted values before calling Evaluate (WP-31).
	Constraint string `json:"constraint,omitempty"`
	// Values lists the discrete values this input may take, for a picker. They
	// come from the input's declared allowed-values enumeration and/or the literal
	// values used in decision-table cells. Empty when the domain is open or
	// continuous (e.g. a numeric range). A consumer can offer these to send a
	// correct input without guessing.
	Values []string `json:"values,omitempty"`
	// ValuesClosed is true when Values is the exhaustive allowed set (a declared
	// enumeration) — a caller may offer a closed dropdown and reject other input;
	// false when Values are merely suggestions inferred from table cells and other
	// values are still accepted.
	ValuesClosed bool `json:"valuesClosed,omitempty"`
}

InputField describes one input a decision expects: its name, its declared FEEL type (empty when the model declares none) and whether the decision requires it. It is the self-description an agent reads before calling Evaluate (ADR-0013, WP-52).

type InputProblem

type InputProblem struct {
	Input    string `json:"input"`
	Code     string `json:"code"`
	Message  string `json:"message"`
	Expected string `json:"expected,omitempty"`
	Got      string `json:"got,omitempty"`
}

InputProblem is a single, machine-readable input-validation failure. Code is one of "TYPE_MISMATCH", "UNKNOWN_INPUT", "MISSING_INPUT" or, for a value outside its type's allowed values, "VALUE_NOT_ALLOWED" (WP-31).

type InvocationBindingView

type InvocationBindingView struct {
	Parameter string `json:"parameter"`
	Value     string `json:"value"`
}

InvocationBindingView is one parameter binding of a boxed invocation: the formal parameter name and the FEEL argument bound to it.

type InvocationEdit

type InvocationEdit struct {
	Called   string                  `json:"called"`
	Bindings []InvocationBindingView `json:"bindings"`
}

InvocationEdit is the editable payload for a boxed invocation: the called function/BKM and its parameter bindings.

type InvocationView

type InvocationView struct {
	DecisionID string                  `json:"decisionId"`
	Name       string                  `json:"name"`
	Called     string                  `json:"called"`
	Bindings   []InvocationBindingView `json:"bindings"`
	Simple     bool                    `json:"simple"`
}

InvocationView is a decision's boxed-invocation logic for the modeler: the called function/BKM (Called, a name) and the parameter bindings supplying its arguments. Simple is false when the called expression or any binding argument is itself a nested boxed expression (not a literal), which this text view cannot represent — the editor then opens read-only so it never clobbers the nesting.

type ItemType

type ItemType struct {
	Name          string     `json:"name"`
	TypeRef       string     `json:"typeRef,omitempty"`
	IsCollection  bool       `json:"isCollection,omitempty"`
	AllowedValues string     `json:"allowedValues,omitempty"`
	Structured    bool       `json:"structured,omitempty"`
	Components    []ItemType `json:"components,omitempty"`
}

ItemType is a model's item definition (a named DMN type) as the modeler edits it: a base FEEL type with an optional collection flag and allowed-values constraint. Structured types (with item components) are reported with Structured=true and are read-only here — the simple editor does not touch them. Components carries a structured type's fields (name + type, nested), so a consumer can show the shape a caller must supply for that type — turning an opaque type name like "tDriverList" into the list of fields it actually wants.

type IteratorEdit

type IteratorEdit struct {
	Kind     string `json:"kind"`
	Variable string `json:"variable"`
	In       string `json:"in"`
	Body     string `json:"body"`
}

IteratorEdit is the editable payload for a boxed iteration: the kind, the iterator variable, the collection and the return/satisfies body.

type IteratorView

type IteratorView struct {
	DecisionID string `json:"decisionId"`
	Name       string `json:"name"`
	Kind       string `json:"kind"` // "for" | "some" | "every"
	Variable   string `json:"variable"`
	In         string `json:"in"`
	Body       string `json:"body"`
	Simple     bool   `json:"simple"`
}

IteratorView is a decision's boxed-iteration logic for the modeler: a `for` (which yields a list via its return branch) or a `some`/`every` quantifier (which yields a boolean via its satisfies branch). Body is that branch's FEEL text; the iterator Variable is bound in it while In is the collection iterated. Simple is false when either branch is a nested boxed expression (not a literal), which this text view cannot represent — the editor then opens read-only so it never clobbers the nesting.

type Limits

type Limits struct {
	MaxCallDepth   int           // nested user-function (BKM / function literal) calls
	MaxIterations  int           // total iteration steps across all comprehensions in one evaluation
	MaxListSize    int           // element count of any single list produced by a comprehension
	CompileTimeout time.Duration // wall-clock budget for Compile when the context has no earlier deadline
}

Limits bounds the resources a single compilation or evaluation may consume, turning hostile input (deep recursion, runaway comprehensions, huge lists, pathological models) into a clean error instead of a hang or out-of-memory (ADR-0008). A zero field falls back to the built-in default for that dimension, so a caller may tighten one limit without restating the rest.

type ListEdit

type ListEdit struct {
	Items []string `json:"items"`
}

ListEdit is the editable payload for a boxed list: the ordered FEEL items.

type ListView

type ListView struct {
	DecisionID string   `json:"decisionId"`
	Name       string   `json:"name"`
	Items      []string `json:"items"`
	Simple     bool     `json:"simple"`
}

ListView is a decision's boxed-list logic for the modeler: an ordered list of FEEL item expressions. Simple is false when any item is itself a nested boxed expression (not a literal), which this text view cannot represent — the editor then opens read-only so it never clobbers the nesting.

type LiteralView

type LiteralView struct {
	DecisionID string `json:"decisionId"`
	Name       string `json:"name"`
	Text       string `json:"text"`
	TypeRef    string `json:"typeRef,omitempty"`
}

LiteralView is a decision's literal-expression logic for the modeler: the FEEL text and its declared result type.

type ModelIndex

type ModelIndex struct {
	Decisions []string
	Inputs    []string
}

ModelIndex lists a model's evaluable decisions and its input data, by name, for tooling and discovery.

type NodeEdit

type NodeEdit struct {
	ID   string  `json:"id"`
	Name *string `json:"name,omitempty"`
	// VarName sets the element's FEEL identifier (decision/inputData variable name),
	// kept distinct from the free-form display Name. A non-nil value that differs
	// from the display name writes an explicit <variable>; one equal to it (or
	// empty) lets the identifier follow the display name (see SetVariableName).
	VarName  *string  `json:"varName,omitempty"`
	DataType *string  `json:"dataType,omitempty"`
	X        *float64 `json:"x,omitempty"`
	Y        *float64 `json:"y,omitempty"`
}

NodeEdit describes a change to one decision requirements graph node, addressed by its DMN element id. Only the non-nil fields are applied, so a client can persist a move without touching the name, or a rename without touching the position. It mirrors the editable subset of GraphNode (ADR-0016, Edit→Save).

type Option

type Option func(*config)

Option configures an Engine passed to New (e.g. WithLimits).

func WithLimits

func WithLimits(l Limits) Option

WithLimits sets the engine's resource limits. Unset (zero) fields keep their defaults; see Limits.

Example

ExampleWithLimits bounds the resources any single evaluation may consume, turning hostile input into a clean error instead of a hang or out-of-memory.

package main

import (
	"context"
	"fmt"

	"github.com/pblumer/temis/dmn"
)

// A minimal DMN 1.5 model: one input (a number) and one decision whose literal
// FEEL expression doubles it. Real models are authored in a DMN editor and
// loaded as standard DMN XML; this one is inlined to keep the example
// self-contained.
const doubleModel = `<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="https://www.omg.org/spec/DMN/20230324/MODEL/"
             namespace="http://temis.example/double" name="Double" id="def_double">
  <inputData id="id_n" name="N">
    <variable name="N" typeRef="number"/>
  </inputData>
  <decision id="id_double" name="Double">
    <variable name="Double" typeRef="number"/>
    <informationRequirement>
      <requiredInput href="#id_n"/>
    </informationRequirement>
    <literalExpression><text>N * 2</text></literalExpression>
  </decision>
</definitions>`

func main() {
	eng := dmn.New(dmn.WithLimits(dmn.Limits{
		MaxCallDepth:  64,
		MaxIterations: 100_000,
		MaxListSize:   100_000,
	}))
	defs, _, err := eng.Compile(context.Background(), []byte(doubleModel))
	if err != nil {
		panic(err)
	}
	dec, _ := defs.Decision("Double")
	res, _ := dec.Evaluate(context.Background(), dmn.Input{"N": 5})
	fmt.Println(res.Outputs["Double"])
}
Output:
10

type RelationEdit

type RelationEdit struct {
	Columns []string   `json:"columns"`
	Rows    [][]string `json:"rows"`
}

RelationEdit is the editable payload for a boxed relation: the column names and the rows of FEEL cells (each row aligned to the columns).

type RelationView

type RelationView struct {
	DecisionID string     `json:"decisionId"`
	Name       string     `json:"name"`
	Columns    []string   `json:"columns"`
	Rows       [][]string `json:"rows"`
	Simple     bool       `json:"simple"`
}

RelationView is a decision's boxed-relation logic for the modeler: named columns and rows of FEEL cells (reference/lookup data). Simple is false when any cell is itself a nested boxed expression (not a literal), which this text grid cannot represent — the editor then opens read-only so it never clobbers the nesting.

type Result

type Result struct {
	// Outputs holds the requested decision's result, keyed by decision name.
	Outputs map[string]any
	// Decisions holds every decision evaluated to produce the result, keyed by
	// name: the requested decision plus each required decision the evaluator ran
	// for it (WP-28). A required value supplied directly in the input is used as
	// given and is not re-evaluated, so it does not appear here.
	Decisions map[string]any
	// Diags holds runtime diagnostics (e.g. a null produced by a recoverable
	// error). Spec-conformant null results are not errors.
	Diags Diagnostics
	// Trace is the structured explanation of this evaluation, present only when
	// the call requested it via WithTrace; nil otherwise.
	Trace *Trace
}

Result is the outcome of evaluating a decision.

type Severity

type Severity int

Severity classifies a Diagnostic.

const (
	SevError Severity = iota
	SevWarning
	SevInfo
)

Diagnostic severities.

func (Severity) String

func (s Severity) String() string

String returns the lowercase severity name.

type TableEdit

type TableEdit struct {
	HitPolicy      string        `json:"hitPolicy,omitempty"`
	Aggregation    string        `json:"aggregation,omitempty"`
	Inputs         []TableInput  `json:"inputs,omitempty"`
	Outputs        []TableOutput `json:"outputs,omitempty"`
	Rules          []TableRule   `json:"rules"`
	ReplaceColumns bool          `json:"replaceColumns,omitempty"`
}

TableEdit is the editable payload for a decision table. Rules are always rewritten. HitPolicy, when non-empty, sets the policy (Aggregation applies to Collect). Inputs/Outputs replace the columns only when ReplaceColumns is set — so a rules-only edit (ReplaceColumns false) keeps the existing columns, while the modeler's full editor sends the columns and sets the flag (ADR-0016).

type TableInput

type TableInput struct {
	Label      string `json:"label,omitempty"`
	Expression string `json:"expression"`
	TypeRef    string `json:"typeRef,omitempty"`
}

TableInput is one input column: the FEEL expression whose value each rule tests, with an optional label and declared type.

type TableOutput

type TableOutput struct {
	Name    string `json:"name,omitempty"`
	Label   string `json:"label,omitempty"`
	TypeRef string `json:"typeRef,omitempty"`
}

TableOutput is one output column.

type TableRule

type TableRule struct {
	InputEntries  []string `json:"inputEntries"`
	OutputEntries []string `json:"outputEntries"`
	Annotations   []string `json:"annotations,omitempty"`
}

TableRule is one rule row: the unary-test input entries (aligned with Inputs), the result output entries (aligned with Outputs) and any annotations.

type TableTrace

type TableTrace struct {
	HitPolicy   string       `json:"hitPolicy"`             // the table's hit policy (U/A/F/R/C)
	Aggregation string       `json:"aggregation,omitempty"` // collect aggregation (SUM/MIN/MAX/COUNT), or "" if none
	Inputs      []TraceInput `json:"inputs"`                // the input columns and the values they produced
	Rules       []TraceRule  `json:"rules"`                 // every rule, with its condition results
	Matched     []int        `json:"matched"`               // indices (0-based) of the rules that matched
}

TableTrace explains one decision table's evaluation.

type TableView

type TableView struct {
	DecisionID  string        `json:"decisionId"`
	Name        string        `json:"name"`
	HitPolicy   string        `json:"hitPolicy"`
	Aggregation string        `json:"aggregation,omitempty"`
	Inputs      []TableInput  `json:"inputs"`
	Outputs     []TableOutput `json:"outputs"`
	Rules       []TableRule   `json:"rules"`
}

TableView is a decision's decision-table logic, flattened for display by the modeler (ADR-0016): the static table — hit policy, input/output columns and rule rows — independent of any evaluation. It carries JSON tags as part of that wire contract.

type Trace

type Trace struct {
	// Tables holds one entry per decision table evaluated, in evaluation order.
	// A decision whose logic is a literal expression (not a table) produces no
	// table entries.
	Tables []TableTrace `json:"tables"`
}

Trace is an optional, structured explanation of an evaluation: which decision tables ran, the input values they tested, which rules matched (and why), and what those rules produced. It lets a caller — notably an AI agent (ADR-0013, WP-51) — justify a decision rather than merely read its output.

A Trace is derived from the actual evaluation, never reconstructed after the fact. It is present on a Result only when WithTrace is passed to Evaluate; the default Evaluate path produces no Trace and stays allocation-free.

Unlike the other dmn types, the Trace tree carries JSON tags: it is the agent-facing explanation that the HTTP and MCP adapters serialise verbatim, so its field names are part of that wire contract.

type TraceCondition

type TraceCondition struct {
	Input   string `json:"input"`
	Entry   string `json:"entry"`
	Matched bool   `json:"matched"`
}

TraceCondition is one input cell's test: the column expression, the rule's unary-test text and whether the input satisfied it. Conditions are reported up to and including the first one that fails (the evaluator short-circuits), so a non-matching rule shows exactly which condition ruled it out.

type TraceInput

type TraceInput struct {
	Expression string `json:"expression"`
	Value      any    `json:"value"`
}

TraceInput is one input column: its FEEL expression and the value it evaluated to, in the same Go form Evaluate uses for outputs.

type TraceRule

type TraceRule struct {
	Index      int              `json:"index"`        // 0-based row position in the table
	ID         string           `json:"id,omitempty"` // model rule id, when the source provides one
	Matched    bool             `json:"matched"`
	Conditions []TraceCondition `json:"conditions"`
	// Outputs holds the rule's output values, set only when the rule actually
	// contributed to the result under the hit policy; nil otherwise.
	Outputs []any `json:"outputs,omitempty"`
}

TraceRule is one rule's contribution to the result.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL