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 ¶
- Constants
- func ApplyEdits(src []byte, edits []NodeEdit) ([]byte, error)
- func ApplyGraph(src []byte, edit GraphEdit) ([]byte, error)
- func ApplyTableEdit(src []byte, decisionID string, edit TableEdit) ([]byte, error)
- func CreateBoxedConditional(src []byte, decisionID string) ([]byte, error)
- func CreateBoxedContext(src []byte, decisionID string) ([]byte, error)
- func CreateBoxedFilter(src []byte, decisionID string) ([]byte, error)
- func CreateBoxedInvocation(src []byte, decisionID string) ([]byte, error)
- func CreateBoxedIterator(src []byte, decisionID string) ([]byte, error)
- func CreateBoxedList(src []byte, decisionID string) ([]byte, error)
- func CreateBoxedRelation(src []byte, decisionID string) ([]byte, error)
- func CreateDecisionTable(src []byte, decisionID string) ([]byte, error)
- func RemoveItemDefinition(src []byte, name string) ([]byte, error)
- func SetBKMFunction(src []byte, bkmID string, edit BKMFunctionEdit) ([]byte, error)
- func SetBoxedConditional(src []byte, decisionID string, edit ConditionalEdit) ([]byte, error)
- func SetBoxedContext(src []byte, decisionID string, edit ContextEdit) ([]byte, error)
- func SetBoxedFilter(src []byte, decisionID string, edit FilterEdit) ([]byte, error)
- func SetBoxedInvocation(src []byte, decisionID string, edit InvocationEdit) ([]byte, error)
- func SetBoxedIterator(src []byte, decisionID string, edit IteratorEdit) ([]byte, error)
- func SetBoxedList(src []byte, decisionID string, edit ListEdit) ([]byte, error)
- func SetBoxedRelation(src []byte, decisionID string, edit RelationEdit) ([]byte, error)
- func SetItemDefinition(src []byte, t ItemType) ([]byte, error)
- func SetLiteralExpression(src []byte, decisionID, text, typeRef string) ([]byte, error)
- func SetLogic(src []byte, a Anchor, at, kind string, raw json.RawMessage) ([]byte, error)
- func SetModelName(src []byte, name string) ([]byte, error)
- type Anchor
- type BKMFunctionEdit
- type BKMParam
- type BKMView
- type CompiledDecision
- func (c *CompiledDecision) Evaluate(ctx context.Context, in Input, opts ...EvalOption) (Result, error)
- func (c *CompiledDecision) ID() string
- func (c *CompiledDecision) InputSchema() []InputField
- func (c *CompiledDecision) Name() string
- func (c *CompiledDecision) ValidateInput(in Input) []InputProblem
- type CompiledExpression
- type CompiledService
- type ConditionalEdit
- type ConditionalView
- type ContextEdit
- type ContextEntryView
- type ContextView
- type Definitions
- func (d *Definitions) BKMFunction(idOrName string) (BKMView, bool)
- func (d *Definitions) BoxedConditional(idOrName string) (ConditionalView, bool)
- func (d *Definitions) BoxedContext(idOrName string) (ContextView, bool)
- func (d *Definitions) BoxedFilter(idOrName string) (FilterView, bool)
- func (d *Definitions) BoxedInvocation(idOrName string) (InvocationView, bool)
- func (d *Definitions) BoxedIterator(idOrName string) (IteratorView, bool)
- func (d *Definitions) BoxedList(idOrName string) (ListView, bool)
- func (d *Definitions) BoxedRelation(idOrName string) (RelationView, bool)
- func (d *Definitions) Decision(idOrName string) (*CompiledDecision, error)
- func (d *Definitions) DecisionTable(idOrName string) (TableView, bool)
- func (d *Definitions) EvaluateGraph(ctx context.Context, in Input, opts ...EvalOption) (GraphResult, error)
- func (d *Definitions) Functions() []FeelFunction
- func (d *Definitions) Graph() Graph
- func (d *Definitions) Index() ModelIndex
- func (d *Definitions) InputSchema(idOrName string) ([]InputField, error)
- func (d *Definitions) ItemDefinitions() []ItemType
- func (d *Definitions) LiteralExpression(idOrName string) (LiteralView, bool)
- func (d *Definitions) LogicView(a Anchor, at, kind string) (any, bool)
- func (d *Definitions) ModelInputSchema() []InputField
- func (d *Definitions) ModelName() string
- func (d *Definitions) ReachableInputSchema(idOrName string) ([]InputField, error)
- func (d *Definitions) Service(idOrName string) (*CompiledService, error)
- func (d *Definitions) ValidateModelInput(in Input) []InputProblem
- func (d *Definitions) ValidateReachableInput(idOrName string, in Input) ([]InputProblem, error)
- type Diagnostic
- type Diagnostics
- type Engine
- type EvalError
- type EvalOption
- type FeelFunction
- type FilterEdit
- type FilterView
- type Graph
- type GraphEdge
- type GraphEdgeEdit
- type GraphEdit
- type GraphNode
- type GraphNodeEdit
- type GraphResult
- type Input
- type InputError
- type InputField
- type InputProblem
- type InvocationBindingView
- type InvocationEdit
- type InvocationView
- type ItemType
- type IteratorEdit
- type IteratorView
- type Limits
- type ListEdit
- type ListView
- type LiteralView
- type ModelIndex
- type NodeEdit
- type Option
- type RelationEdit
- type RelationView
- type Result
- type Severity
- type TableEdit
- type TableInput
- type TableOutput
- type TableRule
- type TableTrace
- type TableView
- type Trace
- type TraceCondition
- type TraceInput
- type TraceRule
Examples ¶
Constants ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 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 ¶
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 ¶
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 (*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.
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 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 ¶
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.
Source Files
¶
- bkm.go
- codes.go
- coerce.go
- conditional.go
- constraint.go
- context.go
- convert.go
- definitions.go
- diagnostics.go
- doc.go
- drg.go
- edit.go
- engine.go
- eval.go
- evalerror.go
- expression.go
- filter.go
- graph.go
- graph_eval.go
- graphedit.go
- invocation.go
- iterator.go
- limits.go
- list.go
- literal.go
- logic.go
- relation.go
- result.go
- schema.go
- service.go
- table.go
- trace.go
- typecheck.go
- types.go