dmn

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Aug 21, 2026 License: AGPL-3.0 Imports: 19 Imported by: 0

Documentation

Overview

Package dmn integrates the temis DMN decision engine (github.com/pblumer/temis) into Atlas, so a BPMN business rule task can delegate a decision and get an answer back.

The integration deliberately mirrors how service tasks reach external workers (ADR-0007), so it inherits the engine's durability guarantees without touching the hot path (ADR-0014):

  • A DMN model is compiled by temis once, at deploy time, into immutable thread-safe decisions held in a Registry (invariant I5: compile, don't interpret — no XML parsing or FEEL compilation at runtime).
  • A business rule task creates a job carrying the reserved DMN job type. The processor never evaluates a decision itself, so it stays allocation-free (invariant I1) and free of the temis dependency.
  • The in-process Handler — a job worker — pulls those jobs, evaluates the decision off the processor goroutine, and completes the job, which drives the token onward through the normal completion path. Evaluation is a post-durability side effect, exactly like any other worker (invariant I2).

Because there is no process-variable subsystem yet (Milestone 1), a business rule task feeds its decision a static input context recorded at deploy time and its outputs are surfaced through a caller-supplied sink rather than written back as variables. Wiring real input/output variable mappings is future work.

Index

Constants

This section is empty.

Variables

View Source
var ErrNotFound = errors.New("dmn: no model matches the reference")

ErrNotFound is returned by a Resolver when no model matches the handle. It is deliberately distinct from an I/O failure so a caller can tell "unresolved reference" (a user-fixable modelRef) apart from "the model source is broken" (an infrastructure error).

Functions

func DecisionHandler

func DecisionHandler(store state.Reader, lookup ProcessLookup, bind Bind, sink func(Result)) job.CompletingHandler

DecisionHandler builds a job handler that evaluates the DMN decision behind a business rule task using the Evaluator that bind resolves for it. It owns the shared input/output-mapping semantics (ADR-0039) so local and central decisions cannot drift: for each job it

  • resolves the decision, static inputs, input mappings, and result variable from the compiled process,
  • builds the decision's input context by merging the static inputs with the input mappings evaluated over the instance's live variables (a mapping wins over a static input of the same name),
  • evaluates the decision through the bound Evaluator, and
  • returns the result as the process variable named by resultVariable, which the job completion writes back into the instance so a downstream gateway can route on it.

Evaluation is a post-durability side effect off the processor goroutine (invariant I2/I4). Returning an error leaves the job pending. Alongside the output variables, the completion carries a durable decision-evaluation record — the inputs, outputs, and trace — so an operator can inspect how the decision was made live and after the fact (ADR-0066). sink, if non-nil, additionally observes each result. Handler (local) and the temis connector worker (central, ADR-0050) are both built on it.

func Handler

func Handler(store state.Reader, lookup ProcessLookup, reg *Registry, sink func(Result)) job.CompletingHandler

Handler builds the in-process, local DMN worker: a DecisionHandler whose Evaluator is the embedded temis library, evaluating each decision against the model deployed under the process's own key (ADR-0014). Register it with a job.Runner via HandleCompleting for the reserved DMN job type (compiler.DMNJobTypeIndex). sink, if non-nil, observes each result.

Types

type Bind

Bind resolves the Evaluator for a business rule task on a compiled process — the decision engine bound to it. A returned error (e.g. an unregistered connector) leaves the job pending like any worker error.

type DecisionField

type DecisionField struct {
	Name string `json:"name"`
	Type string `json:"type,omitempty"`
}

DecisionField is one input or output of a decision, for authoring tooling: a name and its declared FEEL type (empty when the model declares none).

type DecisionInfo

type DecisionInfo struct {
	ID     string          `json:"id"`
	Name   string          `json:"name"`
	Inputs []DecisionField `json:"inputs"`
	Output DecisionField   `json:"output"`
}

DecisionInfo is a decision's self-description for the Modeler's decision picker (ADR-0050): the id to reference it by, its display name, the input data it consumes (so a business rule task's input mappings can be auto-filled), and its output (the process variable a result naturally lands in).

type DeployedDecision

type DeployedDecision struct {
	ID     string
	Name   string
	Model  string
	Inputs []DecisionField
	Output DecisionField
}

DeployedDecision is a decision available from a deployed model, described for the Modeler's decision picker (ADR-0050): its id, declared inputs and output, and the name of the model that provides it. It lets an author select — and auto-fill the inputs of — a decision that is deployed (and thus runnable) even when no separate DMN reference artifact exists for it.

type DirResolver

type DirResolver struct {
	Dir string
}

DirResolver resolves a handle against a directory of DMN files exported from temis: modelRef "risk-score" resolves to <Dir>/risk-score.dmn, falling back to <Dir>/risk-score.xml. It is the zero-config default source; a temis git or service resolver can replace it behind the Resolver interface without touching callers.

func (DirResolver) Resolve

func (r DirResolver) Resolve(_ context.Context, modelRef string) ([]byte, error)

Resolve reads the model file for a handle. A missing model yields ErrNotFound; any other read failure is returned as-is so the caller reports it as an infrastructure error, not an unresolved reference.

type Evaluation

type Evaluation struct {
	Outputs map[string]any
	Trace   []byte
}

Evaluation is what running a decision yields: its named outputs and, when the engine can produce one, the temis trace explaining how it got there — which tables ran and which rules fired (ADR-0066). Trace is canonical JSON or nil when no trace is available (a literal-expression decision, or a remote decision whose connector returns none).

type Evaluator

type Evaluator func(ctx context.Context, decisionId string, inputs map[string]any) (Evaluation, error)

Evaluator evaluates a decision by id against an input context and returns its outputs and (when available) trace. It is the seam between a business rule task's I/O semantics and the engine that runs the decision: the local embedded temis library and a remote temis connector each provide one (ADR-0050).

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 one that requires it — matching the DMN arrow direction. Type is "informationRequirement" or "knowledgeRequirement".

type GraphNode

type GraphNode struct {
	ID       string  `json:"id"`
	Type     string  `json:"type"`
	Name     string  `json:"name"`
	DataType string  `json:"dataType,omitempty"`
	VarName  string  `json:"varName,omitempty"`
	HasTable bool    `json:"hasTable,omitempty"`
	X        float64 `json:"x,omitempty"`
	Y        float64 `json:"y,omitempty"`
	Width    float64 `json:"width,omitempty"`
	Height   float64 `json:"height,omitempty"`
}

GraphNode is one element of a DMN model's decision requirements graph, for a read-only viewer (ADR-0014's non-goal is a DMN *editor*; viewing what a referenced model contains is in scope). Type is "decision", "inputData", or "businessKnowledgeModel". X/Y/Width/Height carry the authored DMNDI bounds when the model has a diagram (all zero otherwise, so the client auto-lays-out).

type ModelGraph

type ModelGraph struct {
	Resolved  bool        `json:"resolved"`
	Valid     bool        `json:"valid"`
	ModelName string      `json:"modelName,omitempty"`
	Message   string      `json:"message,omitempty"`
	Nodes     []GraphNode `json:"nodes"`
	Edges     []GraphEdge `json:"edges"`
}

ModelGraph is a referenced DMN model's requirements graph plus its resolve/valid status, so a viewer can render the diagram or show why it can't. Nodes/Edges are empty (never null) unless the model resolved and compiled cleanly.

type ProcessLookup

type ProcessLookup func(defKey uint64) *compiler.CompiledProcess

ProcessLookup resolves a process-definition key to its compiled process. The worker uses it to find the decision, inputs, and result variable a business-rule job belongs to, so one handler serves every deployed process.

type Registry

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

Registry holds the DMN models deployed alongside process definitions. It compiles each model once with temis and keeps the immutable result, keyed by the owning process-definition key, ready for cheap repeated evaluation.

A Registry is safe for concurrent evaluation once populated. Populate it (via Deploy) before the processes that use it start running.

func NewRegistry

func NewRegistry() *Registry

NewRegistry creates an empty registry over a fresh temis engine.

func (*Registry) Deploy

func (r *Registry) Deploy(defKey uint64, dmnXML []byte) error

Deploy compiles a DMN model and registers it under the process-definition key of the process whose business rule tasks reference it. A process may bundle several models (its tasks can call decisions from different models), so Deploy appends — call it once per bundled model. It also updates the latest-version pointer for every decision the model provides (ADR-0063), so a latest-bound task resolves the newest deployed version. Compilation happens here, at deploy time, never at evaluation time (invariant I5). It returns an error if temis cannot parse or compile the model.

func (*Registry) DeployedDecisions

func (r *Registry) DeployedDecisions() []DeployedDecision

DeployedDecisions describes every decision provided by the newest deployed model that supplies it (ADR-0063 latest binding), for the Modeler's picker. It reads the registry's already-compiled models — no XML parsing or resolver I/O — so it must run on the registry's owning goroutine (the run loop), the same single-writer discipline as Deploy. Results are sorted by decision id for a stable picker.

func (*Registry) Evaluate

func (r *Registry) Evaluate(ctx context.Context, defKey uint64, decisionId string, in map[string]any) (map[string]any, error)

Evaluate runs the named decision from the model deployed under defKey against the given input context and returns its outputs (decision name → value). It is the runtime hot spot of the integration, but it runs on a worker, off the processor goroutine.

func (*Registry) EvaluateLatest

func (r *Registry) EvaluateLatest(ctx context.Context, decisionId string, in map[string]any) (map[string]any, error)

EvaluateLatest runs the named decision from the newest deployed model that provides it (ADR-0063), for a latest-bound business rule task. It is otherwise identical to Evaluate; only the model selection differs.

func (*Registry) EvaluateLatestTraced

func (r *Registry) EvaluateLatestTraced(ctx context.Context, decisionId string, in map[string]any) (map[string]any, []byte, error)

EvaluateLatestTraced is EvaluateLatest plus the temis trace (see EvaluateTraced).

func (*Registry) EvaluateTraced

func (r *Registry) EvaluateTraced(ctx context.Context, defKey uint64, decisionId string, in map[string]any) (map[string]any, []byte, error)

EvaluateTraced is Evaluate plus the temis trace explaining how the decision was made — which tables ran, which rules matched, and why (ADR-0066). The trace is canonical JSON (temis's tdmn.Trace tree) or nil for a decision with no table logic (a literal expression). The DMN worker uses it to retain a debuggable record of the evaluation. Tracing runs off the processor goroutine, so its extra allocation is not on any hot path (temis's WithTrace, ADR-0013/WP-51).

type Resolver

type Resolver interface {
	Resolve(ctx context.Context, modelRef string) ([]byte, error)
}

Resolver turns a DMN reference handle — the modelRef an Atlas project stores (ADR-0034) — into the DMN model XML authored in temis. It is the seam between "this project references decision X" and "here is X's model": a filesystem folder of temis-exported models today, a temis git repo or service later. Implementations must be safe for concurrent use.

type Result

type Result struct {
	ElementInstanceKey uint64
	ProcessDefKey      uint64
	DecisionId         string
	Inputs             map[string]any
	Outputs            map[string]any
	Trace              []byte // temis trace JSON explaining the evaluation; nil if none
}

Result is one evaluated business rule task's outcome, delivered to the optional sink a Handler is built with. The decision's outputs are written back into the instance as process variables (see Handler), and the full evaluation (inputs, outputs, trace) is retained as a durable debugging record (ADR-0066); the sink is an additional observation seam for tests and diagnostics, not the primary path.

type ServiceResolver

type ServiceResolver struct {
	// BaseURL is the model source root; the handle "risk-score" resolves to
	// <BaseURL>/risk-score.dmn.
	BaseURL string
	// Client is the HTTP client to use; nil uses a client bounded by the shared
	// connector call budget (nettimeout.Default), never an unbounded one — this
	// resolver is called from the DMN worker on the run-loop goroutine.
	Client *http.Client
	// Token, if set, is sent as an "Authorization: Bearer <Token>" header — the
	// credential for a private temis service or git host.
	Token string
}

ServiceResolver resolves a DMN reference handle against a temis model source reachable over HTTP: it GETs <BaseURL>/<modelRef>.dmn and returns the body. It is the networked alternative to DirResolver behind the same Resolver interface — a temis git host (raw file URLs) or a temis model service both fit this shape, so which source Atlas reads from is a deployment choice, not a code change (ADR-0034/0014).

A 404 is reported as ErrNotFound (an unresolved, user-fixable reference); any other non-2xx response or a transport failure is returned as an infrastructure error, so a caller can tell "no such model" from "the model source is broken". A ServiceResolver is safe for concurrent use.

func (ServiceResolver) Resolve

func (r ServiceResolver) Resolve(ctx context.Context, modelRef string) ([]byte, error)

Resolve fetches the model file for a handle from the service. A missing model (404) yields ErrNotFound; any other non-2xx status or a transport error is returned as-is so the caller reports it as an infrastructure failure, not an unresolved reference. The handle is validated exactly as DirResolver validates it, so it can never escape BaseURL's path.

type ValidationResult

type ValidationResult struct {
	Resolved  bool     // the modelRef resolved to an actual model
	Valid     bool     // the resolved model compiled without errors in temis
	ModelName string   // the DMN <definitions name>, when resolved
	Decisions []string // decision names the model exposes, when valid
	Message   string   // human-readable reason when unresolved or invalid
}

ValidationResult reports the outcome of resolving a DMN reference and checking it against temis (ADR-0034 Phase 2). It is a pure preflight result: no engine or registry state is mutated by producing it.

type Validator

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

Validator resolves DMN references and validates them against temis. It owns a temis engine used only to compile (nothing is deployed) and a Resolver for fetching model XML. Safe for concurrent use once constructed.

func NewValidator

func NewValidator(resolver Resolver) *Validator

NewValidator builds a Validator over a resolver and a fresh temis engine.

func (*Validator) Describe

func (v *Validator) Describe(ctx context.Context, modelRef string) (string, []DecisionInfo, error)

Describe resolves modelRef, compiles it, and returns its model name and the self-description of each evaluable decision (inputs + output). Like Validate it returns a non-nil error only for an infrastructure failure; an unresolved handle or an invalid model yields an empty result (a best-effort catalog entry), not an error, so one broken reference does not blank the whole picker.

func (*Validator) Graph

func (v *Validator) Graph(ctx context.Context, modelRef string) (ModelGraph, error)

Graph resolves modelRef, compiles it, and returns its decision requirements graph for a read-only viewer. Like Validate it returns a non-nil error only for an infrastructure failure; an unresolved handle or an invalid model is a normal result carrying a Message (and no nodes), so the viewer can explain the state instead of erroring.

func (*Validator) Validate

func (v *Validator) Validate(ctx context.Context, modelRef string) (ValidationResult, error)

Validate resolves modelRef and compiles it with temis, reporting whether it resolved and whether it is a valid DMN model — the check a deploy runs before trusting a reference. It returns a non-nil error ONLY for an infrastructure failure (e.g. the model source is unreadable); an unresolved handle or an invalid model is a normal, reportable result, not an error, so a caller can surface it to the user rather than as a 500.

func (*Validator) ValidateXML

func (v *Validator) ValidateXML(ctx context.Context, xml []byte) ValidationResult

ValidateXML compiles a DMN model already in hand (not resolved by a handle) and reports whether it is valid, with its name and decisions — the check the upload path runs before storing a model. Resolved is always true (the bytes are present), and no infrastructure failure is possible, so it returns no error.

Jump to

Keyboard shortcuts

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