Help us shape the future of Go! Take the 2026 Go Developer Survey

dmn

package
v0.21.0 Latest Latest
Warning

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

Go to latest
Published: Oct 29, 2026 License: AGPL-3.0 Imports: 23 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), and which deployment's model a task evaluates is settled there too (ADR-0319), so the runtime and a replay choose no version.
  • 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).

A business rule task's input context is the static inputs recorded at deploy time overlaid with its io-mapping inputs, FEEL-evaluated over the instance's variables off the hot path, and its result is written back into the process variable the task names (ADR-0039). Each evaluation also rides back as a durable record carrying its inputs, outputs and temis trace (ADR-0066); the caller-supplied sink is an additional observation seam for tests and diagnostics, not the path the result travels.

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 BuildInputs added in v0.5.0

func BuildInputs(store state.Reader, elementKey, piKey uint64, staticJSON string, mappings []compiler.DecisionInputMapping) (map[string]any, error)

buildInputs assembles a decision's input context: the static constant inputs as a base, overlaid with the input mappings evaluated over the instance's live variables (a mapping overrides a static input of the same name). A nil result evaluates every referenced decision input to FEEL null.

The mappings resolve over the business rule task's full scope chain (elementKey up to the process root), not just the process scope, so a task nested in a subprocess or a multi-instance body reads its enclosing scope's variables — e.g. a per-row `inputElement` bound by a multi-instance loop (ADR-0068 scope-chain resolution, ADR-0077, ADR-0084). piKey binds the reserved processInstanceKey.

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 worker (central, ADR-0050) are both built on it.

func EnsureDiagram added in v0.7.0

func EnsureDiagram(src []byte) []byte

EnsureDiagram completes a DMN model's diagram so dmn-js can draw it, and returns the model unchanged when it already has one. Best-effort: anything it cannot parse or lay out comes back untouched rather than mangled.

func EnsureDiagramReport added in v0.7.0

func EnsureDiagramReport(src []byte) ([]byte, bool)

EnsureDiagramReport is EnsureDiagram, additionally reporting whether it drew anything — which a caller storing the result wants to know.

Three cases, and the third is the one this exists for:

  • the diagram covers every node → unchanged. Somebody placed these.
  • there is no diagram → one is generated.
  • the diagram covers *some* nodes → the whole graph is laid out afresh.

A partial diagram is not a half-finished human arrangement; no DMN tool emits one on purpose. It is the residue of a tool that drew what it could and saved back only that, so the coordinates in it were invented by the thing that could not draw the rest. Completing around them would mean placing the missing nodes in whatever space was left rather than laying out the graph the model describes.

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.

func JSONObject added in v0.5.0

func JSONObject(m map[string]any) string

jsonObject renders a decision's input or output context as canonical JSON for a durable evaluation record. An empty context is recorded as "{}" (an object with no members) rather than JSON null, so the debugging view always shows an object; a marshal failure degrades the same way rather than failing the completion.

func OutputVariable added in v0.5.0

func OutputVariable(name string, outputs map[string]any) model.VariableValue

outputVariable turns a decision's outputs into the process variable named by the task's result variable. A single-output decision stores that value directly (so a condition reads it as a scalar); a multi-output decision stores the whole output map as a structured (JSON) context. The value is canonicalized through the same expr path as any other variable, so it round-trips on replay.

func RegenerateDiagram added in v0.7.0

func RegenerateDiagram(src []byte) []byte

RegenerateDiagram discards whatever diagram the model carries and lays the whole graph out afresh. It backs the editor's Auto-layout action: the author asking for it, which is the one thing that moves a diagram somebody placed.

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 worker) 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"`
	// Service marks a decision service rather than a decision: the same shape,
	// because a business rule task calls either the same way, but an author choosing
	// one should see which is the published interface and which is a decision inside
	// it (services.go).
	Service bool `json:"service,omitempty"`
	// Members are the decisions a service publishes or evaluates internally, under
	// the names a caller addresses them by; empty on a decision, which has none.
	//
	// It is here so a picker can show a decision as belonging to a service. Calling
	// one of them works and answers correctly, which is exactly why it needs saying:
	// it reaches past the interface the service exists to be, and ties the caller to
	// an arrangement the service was meant to be free to change.
	//
	// Input decisions are deliberately absent. One names the boundary the *caller*
	// supplies, so it sits outside the service rather than within it (DMN §10.4).
	Members []string `json:"members,omitempty"`
	// Internal is the subset of Members the service *encapsulates* — the decisions it
	// evaluates on the way to its answer, as opposed to the output decisions it
	// publishes (DMN §10.4 splits the two, and Members deliberately merges them
	// because both are "made of").
	//
	// The difference is the whole point of a service. An output decision is what the
	// service answers with, so calling it directly gets the same value by a longer
	// route. An encapsulated one is a working, and a caller that addresses it has
	// bound itself to an arrangement the service exists to be free to change — the
	// interface is then a claim rather than a boundary. A caller cannot see which it
	// is from the name, so this says it.
	Internal []string `json:"internal,omitempty"`
}

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
	// Service marks a decision service — the published interface over part of a
	// model rather than one decision in it. A task calls either the same way.
	Service bool
}

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
	// Key is the deployment whose model answered — a decision deployment, or the
	// process's own key for a model bundled with it — so the evaluation record can
	// say which version ran. 0 when the lookup that answered cannot name it (the
	// runtime pointer of a definition deployed before ADR-0319).
	Key uint64
}

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 worker 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 worker 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 Atlas has deployed. It compiles each model once with temis and keeps the immutable result, keyed by the deployment it belongs to, ready for cheap repeated evaluation.

Two kinds of deployment put a model here, and the key space is the same one (the server's definition keys), so one map serves both:

  • a **process deployment** bundles the models its business rule tasks need and registers them under the process-definition key (ADR-0014);
  • a **decision deployment** is a decision published in its own right, durable and versioned, under a key of its own (ADR-0319).

A Registry is read and written concurrently, and says so rather than assuming otherwise. It is populated at runtime — every decision deploy registers a model on the run loop — while business rule tasks are evaluated off it, by job handlers running outside the loop (job.Runner.Work). Its indexes are therefore behind a read–write lock. A compiled model is immutable, so the lock covers the lookup and not the evaluation: a reader takes what it needs and evaluates after letting go.

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. Compilation happens here, at deploy time, never at evaluation time (invariant I5). It returns an error if temis cannot parse or compile the model.

It does *not* make this model the newest version of the decisions it provides: that is Registry.DeployDecision's job, and keeping the two apart is what stops deploying a process from silently becoming "a new version of the decision" (ADR-0319). The one index a bundled model still moves is the legacy runtime-latest pointer the definitions deployed before pinning read — see the Registry field comments.

func (*Registry) DeployDecision added in v0.7.0

func (r *Registry) DeployDecision(key uint64, dmnXML []byte) error

DeployDecision compiles a DMN model published as a decision deployment — a decision in its own right rather than a model bundled with some process — and registers it under its own definition key (ADR-0319). It additionally makes that key the newest deployed version of every decision the model provides, which is what a latest-bound business rule task is pinned against when its process is deployed.

Like Deploy it is the deploy-time gate: a model temis cannot compile, or whose diagnostics carry errors, is refused and leaves nothing behind (invariant I5 — compilation happens here, never at evaluation time).

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).

func (*Registry) Graph added in v0.7.0

func (r *Registry) Graph(defKey uint64, decisionId string) (ModelGraph, bool)

Graph returns the requirements graph of the model that the deployment under defKey evaluates decisionId from — the picture of the decision as the evaluation saw it, not as the design-time model reads today. It is the graph frozen at registration, so this is a map lookup: it runs on the run loop, where compiling anything is out of the question (invariants I1/I5).

defKey 0, or a key whose deployment is gone, falls back to the newest model providing the decision. That is the same fallback a latest-bound task evaluates under, and it is honest about what it can still answer: a process definition deleted after its instances ran leaves evaluations behind whose model is no longer addressable any other way. The second return is false when nothing on this server provides the decision at all.

func (*Registry) IsService added in v0.7.0

func (r *Registry) IsService(defKey uint64, decisionId string) bool

IsService reports whether the name addresses a decision *service* — DMN's published interface over part of the graph — rather than one decision in it, in the model the deployment under defKey resolves it from. It answers one question a reader of an evaluation cannot otherwise settle: a service evaluation retains no rule trace (ADR-0398), and "no rules were recorded" and "no rules ran" are not the same thing to say to somebody asking how a case was decided.

func (*Registry) LatestDecisionIDs added in v0.7.0

func (r *Registry) LatestDecisionIDs() map[string]bool

LatestDecisionIDs returns the set of decision ids that have a decision deployment — the ones a latest-bound reference resolves to something other than the model bundled with its own process (ADR-0327).

The deploy-time gate reads it to tell a business rule task that can evaluate from one that cannot: a latest-bound reference in this set needs no model bundled, because pinDecisions will resolve it to that deployment's key. It reads registry state, so it runs on the registry's owning goroutine — the run loop — and the caller hands the answer to the off-loop preflight.

func (*Registry) LatestDecisionKey added in v0.7.0

func (r *Registry) LatestDecisionKey(decisionId string) (uint64, bool)

LatestDecisionKey returns the key of the newest decision deployment providing the decision id, and ok=false when the decision has never been deployed on its own. It is the deploy-time selector behind latest binding: a process deployment resolves each latest-bound reference through it once and stores the answer, so nothing is re-decided at task activation or on replay (invariants I5/I6).

It reads registry state, so it runs on the registry's owning goroutine — the run loop — the same discipline as Deploy.

func (*Registry) Provides added in v0.8.0

func (r *Registry) Provides(defKey uint64, decisionId string) bool

Provides reports whether a model registered under the key provides the decision — for a process deployment, whether the process carries its own copy of it.

func (*Registry) Reload added in v0.4.0

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

Reload is Deploy for a model that is *already* deployed — one snapshotted into a deployment record, coming back at startup — and so it does not re-apply the deploy-time gate above (ADR-0177). Refusing the model here would not undeploy anything; it would only keep the server from starting, with every other definition and every running instance behind it, because a diagnostic that did not exist when the model was deployed exists now.

The model is registered and the error diagnostics are returned rendered for display ("" when there are none), for the caller to report. Rendered, not structured, because temis documents diagnostic messages as human-readable and explicitly not a stable API: they are for an operator to read, not for code to act on.

This is safe because of what temis guarantees about a diagnostic: malformed XML is a hard error, but per-decision problems leave the rest of the model compiled, and a decision whose logic failed to compile is "present but not executable" — so evaluating *that* decision fails the way a failing decision has always failed, as a job error on a worker, while every other decision in the model still answers. A hard compile error still returns an error here: there is no model to bring back.

func (*Registry) ReloadDecision added in v0.7.0

func (r *Registry) ReloadDecision(key uint64, dmnXML []byte) (string, error)

ReloadDecision is DeployDecision for a decision that is *already* deployed — one coming back from its durable record at startup — and so it does not re-apply the deploy-time gate, for the reason Registry.Reload gives (ADR-0177): refusing it here undeploys nothing, it only keeps the server from starting. The rendered error diagnostics are returned ("" when there are none) for the caller to report.

func (*Registry) UndeployDecision added in v0.7.0

func (r *Registry) UndeployDecision(key uint64)

UndeployDecision removes a decision deployment from the registry and leaves the two "newest model providing this decision" pointers exactly as a restart would build them — which is the only definition of correct available here (ADR-0336).

Removing the key is the easy half. The hard half is that both pointers are last-write-wins: each holds *the newest* provider and keeps no history, so a pointer aimed at the removed key cannot simply be cleared — it has to fall back to the next-newest, and nothing here remembers which that was.

It is rebuilt rather than remembered. Keys come from one monotonic counter, so ascending key order *is* registration order, live and on recovery alike; walking the survivors in that order reproduces exactly the state the same records would produce after a restart. That is what makes a delete survivable: what the server answers now and what it answers after rebooting cannot diverge.

Removing a key that is not a decision deployment — a process's bundled model, or nothing at all — is a no-op, so an unknown key is not an error here.

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
	// worker 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 Standalone added in v0.20.0

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

Standalone is one compiled decision held outside the registry, by a caller that owns its lifecycle: an authorization policy embeds a decision table and is compiled when the policy is published (ADR-0475 §6).

It is not a deployment. Nothing is keyed or registered, so publishing a policy can never become "a new version" of a decision a process is bound to (ADR-0319), and the one temis engine stays behind this package, as FEEL stays behind expr (ADR-0015).

A compiled model is immutable, so a Standalone is safe for concurrent evaluation.

func CompileStandalone added in v0.20.0

func CompileStandalone(ctx context.Context, src []byte, decisionID string) (*Standalone, error)

CompileStandalone compiles src once and checks that it provides decisionID. A model with errors, or without that decision, is refused here rather than at the first evaluation: publication is where an author can still fix it (invariant I5).

func (*Standalone) Evaluate added in v0.20.0

func (s *Standalone) Evaluate(ctx context.Context, in map[string]any) (any, error)

Evaluate runs the decision over in and returns the decision's own result: a context (map) for a table with several outputs, the bare value for one with a single output. It is the registry's evaluation path — aliased input names, the type check that refuses a mismatched input instead of letting every rule quietly miss, and numbers restored from their decimal strings — so a decision answers here exactly as it would on a business rule task.

type Trial added in v0.7.0

type Trial struct {
	OK      bool   `json:"ok"`
	Message string `json:"message,omitempty"`

	// What the model offers, whenever it compiled: the panel's form is built from
	// this, so the fields are temis's own view of the model rather than something a
	// client re-derived from the XML.
	ModelName string         `json:"modelName,omitempty"`
	Decisions []DecisionInfo `json:"decisions"`

	// What one decision produced. Present only when a decision was named and ran.
	DecisionID string          `json:"decisionId,omitempty"`
	Outputs    map[string]any  `json:"outputs,omitempty"`
	Trace      json.RawMessage `json:"trace,omitempty"`
}

Trial is the answer to "what does this model do with these inputs": what it can run and what that wants, and — when a decision was named — what it produced.

OK is false for a model that does not compile, a decision id the model does not provide, and an evaluation that errored, with Message saying which. Those are the normal output of authoring rather than a caller's mistake, so they come back as a result to render, not as an HTTP error (the grammar POST /api/v1/feel/evaluate already uses).

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
	// Aliases lists the further names those decisions answer to — a decision's FEEL
	// identifier (its <variable name>) wherever it differs from the name above.
	// Matching a business rule task's decisionId reads both; a listing and a
	// deployment record read Decisions alone, so a decision is published under one
	// name and one only (names.go).
	Aliases []string
	// Services lists the decision services the model publishes — DMN's interface
	// over part of a DRD, which a business rule task addresses exactly as it
	// addresses a decision (services.go).
	Services []string
	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 everything the model offers a caller by name — its decision services and its evaluable decisions, each with its inputs and 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.

Every node comes back with bounds: a model whose diagram does not cover it is completed on the way through, so the viewer never has to invent a layout of its own (ADR-0325).

func (*Validator) Try added in v0.7.0

func (v *Validator) Try(ctx context.Context, src []byte, decisionID string, inputs map[string]any) Trial

Try compiles src with the validator's own temis engine — the one that exists to compile without deploying — and, when decisionID names a decision the model provides, evaluates it over inputs and returns its outputs with the trace.

With decisionID empty it evaluates nothing and only describes the model. That is one question asked twice over, not two modes: the panel needs to know what it may run and what that wants before it can ask for a run, and both answers come out of the same compile.

inputs carries decoded JSON, the same value shapes a business rule task's static inputs arrive as, so a decision tried here sees what it would see at runtime.

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